6

This code executes fine when Run as a SAS program:

%MyMacro(foo_val, bar_val, bat_val);

I have created a table using:

DATA analyses;
   input title : $32. weight : $32. response : $32.;
   datalines;
foo1 bar1 bat1
foo2 bar2 bat2
;

I want to execute MyMacro once for each row of the analyses table.

The following code appears to only pass the string values title, weight and response (rather than the data values foo1 etc.) to my macro (tested with calls to the %put command) :

DATA _NULL_ ;
    set analyses;
    %MyMacro(title, weight, response);

RUN;

How can I invoke the macro once per record of the analyses table whilst passing data values as arguments to the macro? The intention is to actually run this for a very large number of analyses so the solution must scale appropriately to many more records in the analyses table.

JustinJDavies
  • 2,663
  • 4
  • 30
  • 52

3 Answers3

13

This in part depends on what your macro is doing. If we assume that your macro is doing something that is intended to be run outside of a data step (ie, it's not just assigning a data step variable), then you have several options.

CALL EXECUTE has already been explained, and is a good option for some cases. It has some downsides, however, particularly with macro timing, that requires some extra care to protect in some cases - particularly when you are creating macro variables inside your macro. Quentin in his comments shows a way to get around this (adding %NRSTR to the call), but I find that I prefer to only use CALL EXECUTE when there's an advantage to doing so over the other methods - particularly, if I want to use SAS data step techniques (such as FIRST or LAST, for example, or some form of looping) in creating my macro calls, or when I have to do things in a data step anyway and can avoid the overhead of reading the file another time. If I'm just writing a data step like yours above - data something, set something, call execute, run - I wouldn't use it.


PROC SQL SELECT INTO is typically what I use for list processing (which is largely what this is). I like SQL's simplicity a bit better when doing things that aren't too complicated; for example, you can get just one version of each macro call easily with DISTINCT without having to explicitly write a proc sort nodupkey or use first/last processing. It also has the advantage for debugging that you can write all of your macro calls to your results window (if you don't add noprint), which is a bit easier to read than the log for me if I'm trying to see why my calls didn't get generated properly (and doesn't take any extra PUT statements).

proc sql;
  select catx(',','%macro(',arg1,arg2,arg3)||')' 
    into :mvarlist separated by ' '
    from dataset;
quit;

&mvarlist.

That runs them quite simply, and has no timing issues (As you're just writing a bunch of macro calls out).

The main downside to this method is that you have a maximum of 64k characters in a macro variable, so if you're writing a huge number of these you'll run into that. In that case use CALL EXECUTE or %INCLUDE files.


%INCLUDE files are largely useful either as replacement for SELECT INTO when the call is over the character limit, or if you find it useful to have a text file to look at with your calls (if you're running this in batch mode for example, this could be easier to get to and/or parse than log or listing output). You just write your calls out to a file, and then %INCLUDE that file.

filename myfile temp; *or a real file if you want to look at it.;
data _null_;
 set dataset;
 file myfile;
 length str $200;
 str=catx(',','%macro(',arg1,arg2,arg3)||')';
 put str;
run;

%include myfile;

I don't really use this much anymore, but it's a common technique used particularly by older SAS programmers so good to know.


DOSUBL is a relatively new method, and to some extent can be used to replace CALL EXECUTE as its default behavior is typically closer to what you expect intuitively than CALL EXECUTE's. The doc page has really the best example for how this works differently; basically, it fixes the timing issue by letting each separate call look import and export the macro variables from/to the calling environment, meaning that each iteration of DOSUBL is run at a distinct time versus CALL EXECUTE where everything is run in one bunch and the macro environment is 'fixed' (ie, any reference to a macro variable is fixed at run time, unless you escape it messily with %NRSTR).


One more thing worth mentioning is RUN_MACRO, a part of the FCMP language. That allows you to completely run a macro and import its contents back to the data step, which is an interesting option in some cases (for example, you could wrap a call around a PROC SQL that selected a count of something, and then import that to the dataset as a variable, all in one datastep). It's applicable if you're doing this for the purpose of calling a macro to assign a data step variable, not to run a process that does things that don't need to be imported into the data step, but it's something worth considering if you do want that data back all in the dataset that called the process.

Tom
  • 47,574
  • 2
  • 16
  • 29
Joe
  • 62,789
  • 6
  • 49
  • 67
  • 1
    Very nice summary. I'm still hoping that more documentation will be coming from SAS re DOSUBL and RUN_MACRO. It seems to me both of these somehow execute code in a separate mini-SAS session. And when I played with it a couple years ago, the scoping rules weren't very clear. For example, system options set in a macro invoked with run_macro might or might not change the options of the main session. But they're certainly powerful tools, worth learning. – Quentin Aug 28 '14 at 16:37
  • Nice answer. Also worth considering is- can the macro be rewritten as an FCMP function instead? – Robert Penridge Aug 28 '14 at 16:49
  • I look forward to the day where FCMP functions outnumber macros... although in this case, it sounds like it's a complete analysis, which probably is better off not being written that way for now (while RUN_MACRO allows it to be written that way, i'm not convinced that's really a good idea if there's no other major benefit to it). – Joe Aug 28 '14 at 17:10
7

You could use CALL EXECUTE:

data _null_;
  set analyses;
  call execute('%nrstr(%MyMacro('||title||','||weight||','||response||'))');
run;
DavB
  • 1,676
  • 2
  • 14
  • 16
  • 1
    I like call execute approach. Note the macro will be executed (all macro symbols resolved) while the data _null_ step is executing. This means if %MyMacro generates macro variables from data step code, it wont work. Usually it is helpful to add %nrstr(), which will have call execute generate the macro call, but macro won't be executed until after the data _null_ step has completed, e.g.: call execute('%nrstr(%MyMacro('||title||','||weight||','||response||'))'); see http://support.sas.com/kb/23/134.html – Quentin Aug 28 '14 at 13:58
  • 2
    see also DOSUBL as alternative to CALL EXECUTE, referenced in that same support page, apparently manages the timing issue without the need to wrap macro call in %NRSTR() – Quentin Aug 28 '14 at 14:04
0

You can put the variables values into macrovariables and then call your %MyMacro many times (the number of obs in your dataset) with the macrovariables as argument:

Data :

DATA analyses;
   input title : $32. weight : $32. response : $32.;
   datalines;
foo1 bar1 bat1
foo2 bar2 bat2
;
run;

Code to run macro :

data _NULL_;
    set analyses end=fine;
    call symput("ARGUMENT"||compress(_N_),catx(",",title,weight,response));
    if fine then call symput("NLOOPS",compress(_N_));
run;
%*PUT &ARGUMENT1;
%*PUT &ARGUMENT2;

%MACRO MAIN;
%DO L=1 %TO &NLOOPS;
    %MyMacro(&&ARGUMENT&L);
%END;
%MEND;
%MAIN;
JustinJDavies
  • 2,663
  • 4
  • 30
  • 52
user3645882
  • 739
  • 5
  • 11
  • Seems very hacky and I don't understand why I can't call the macro from within a `DATA` step - could you explain why this approach is necessary? – JustinJDavies Aug 28 '14 at 13:00
  • Changed second `%DO` to `%TO` – JustinJDavies Aug 28 '14 at 13:19
  • 1
    Your approach failed because of timing issues. Macro statements are executed before data step code is executed. This is because the job of the macro language is to generate SAS code. In your example, the macro sees title as a text string argument. It does not know there is a dataset variable named title that has a value. The call execute approach is more like what you expected. It is a data step statement which when it is execute, can invoke a macro, and can pass arguments from dataset values. There are interesting timing issues there as well. (see my comment there). – Quentin Aug 28 '14 at 13:53
  • @Quentin is correct on timing. Macros write code for you. The macro processor executes and then passes the resulting string to the SAS compiler to compile and run. – DomPazz Aug 28 '14 at 15:06
  • Yeah it was my bad, I removed an additional comment because I was getting an error (it was because I'd changed the varname fine to something else in only one of the two places so I wasn't executing the last line of the DATA step ever) – JustinJDavies Aug 28 '14 at 15:26
  • Ohh, now I realize I completely misunderstood Quentin's comment - it was a reply to you, not to user3645882. So ignore me. :) – Joe Aug 28 '14 at 15:35
  • I don't like this method, because it's unnecessarily complicated and messy. – Joe Aug 28 '14 at 15:37