2
votes

The following example is very simple and might be solvable in an easier way. However, I am interested in making it work. The following example is based on the cars-dataset of the sashelp-library. First, I have a macro called fun:

proc contents data = sashelp.cars out = mycontents;
run;

%macro fun(var);
proc sql noprint;
        select count(distinct(&var.))
        into :obs
        from sashelp.cars;
quit;
%mend;

Now I want to call the macro but only to update obs (from the input statement). I use:

data work.test;
set mycontents;
if name ne "Type" then do;
      call execute('%nrstr(%fun('||name||');');
      new = &obs;
end;
else new = 5;

run;

In short, this should iterate the rows of the mycontents. Then call one (of several) macros depending on name, which updates obs. Then I can simply populate the new column new with obs. However, obs stays the same value for all names, which is the value from the last variable.

1
Good question, and well written!Joe
@Snorehorse (1) The title doesn't need to tell that it's a SAS question - the tag does that. (On google, for example, the tag will appear along with the page.)Joe
(2) I didn't think CALL EXECUTE was important to the question; the title above is a bit broader and will pick up more hits, and CALL EXECUTE will trigger this post anyway as it's in the body. And i'll add the space, you're correct there.Joe
And if you like dosubl look up "Run_MACRO Run!" which is a very good paper on the more advanced use of it.Joe
Update I am not super happy with either, but define declare etc. isn't great either, and any other solution is more wordy, no? (I don't remember what word you used, so maybe I'm missing something...) Immediately is accurate, I think; immediately as opposed to in the next data step. (could say in the same data step but that's wordy, not searchable, and won't tend to be read anyway as it's so far into the title)Joe

1 Answers

6
votes

Here the problem is twofold.

First, you cannot use CALL EXECUTE in this context because that doesn't execute until after the data step has completed running: so anything depending on &obs will not be able to get an updated value for that. You'll have to use dosubl.

Second, you need to use symget('obs'), not &obs, if you want to get an updated value mid-data step. &obs will resolve when the data step is compiled, so it cannot change during execution; but symget(obs) instructs the data step to query the symbol table during execution.

Here's an example of doing this with dosubl, minimally changed from your example. Note the %global statement to make sure obs is available to us in the data step (there are other ways to do this to get it back that are better - namely, wrapping this in a fcmp function and using run_macro - but this is closest to how you do it).

proc contents data = sashelp.cars out = mycontents;
run;

%macro fun(var);
%global obs;
proc sql noprint;
        select count(distinct(&var.))
        into :obs
        from sashelp.cars;
quit;
%mend;

data work.test;
set mycontents;
if name ne "Type" then do;
      rc = dosubl(cats('%fun(',name,')'));
      new = symgetn('obs');
end;
else new = 5;
run;