Search code examples
sassas-macro

SAS: Running macros inside do loop does not seem to work


I need to run a series of macros multiple times. To do so, I've built a new macro that has inside of it a do-loop, which is supposed to run "i" number of times, and with each iteration of the do-loop, the series of macros referenced above are supposed to run.

Here is the essence of code (note that the first proc sql essentially takes from a dataset called "DatesRange" and placed a range of dates into the variable "varlist_Dates"; the range of dates within this variable is used for Macro1, Macro2, Macro3).

%macro MultipleTimes;

  proc sql noprint;
    select distinct Date
    into :varlist_Dates separated by ' '
    from DatesRange
    order by Date;
  quit; 

  %do i = 1 %to 5;
    %let date = %scan(&varlist_Dates.,&i.);
    %Macro1;
    %Macro2;
    %Macro3;
  %end;

%mend;

I'm finding that it stops at i=1 and never proceeds. I'm completely unclear on why. I've experimented by playing around with removing some macros and keeping others but nothing seems to work. I feel like there may be something more fundamental about my methodology that is off, because there's something I don't know about SAS and about the way a macro inside of a do-loop inside of a macro works.

Any help would be much appreciated. Thanks!


Solution

  • First: check if i is used in any of those macros. I bet it is. It's probably being changed to something higher than 5 (thus exiting after one loop).

    In SAS, when you reference a macro variable that already exists, it is defined as having the scope of the most local symbol table that contains it, unless you use %local to specify it as local to the current macro. So if you throw %let i=1 to 5; around in multiple nested macros, it will be using the same &i.

    As an example, see the following code:

    %let i=A;
    %macro outer;
      %put &=i;
      %do i=1 %to 5;
        %put OUTER FIRST: &=i;
        %inner;
        %put OUTER LAST: &=i;
      %end;
    %mend outer;
    %macro inner;
      %do i=1 %to 5;
        %put INNER: &=i.;
      %end;
    %mend;
    %put GLOBAL FIRST: &=i;
    %outer;
    %put GLOBAL LAST:&=i;
    

    Notice how &i is always the same value. That's not what you mean, now is it?

    Now, on the other hand, &i does get a different value in each bit if you do it right:

    %let i=A;
    %macro outer;
      %local i;
      %put &=i;
      %do i=1 %to 5;
        %put OUTER FIRST: &=i;
        %inner;
        %put OUTER LAST: &=i;
      %end;
    %mend outer;
    %macro inner;
      %local i;
      %do i=1 %to 5;
        %put INNER: &=i.;
      %end;
    %mend;
    %put GLOBAL FIRST: &=i;
    %outer;
    %put GLOBAL LAST:&=i;
    

    And if you %put _all_ in INNER you will see this in action.


    Second: Don't write it this way. You're going to so much effort, just write it a better way to begin with.

    %Macro RunMyMacros(date=);
      %Macro1;  *I hope you use &date as a parameter in these and not as a global;
      %Macro2;
      %Macro3;
    %mend RunMyMacros;
    
    proc sql noprint;
      select distinct cats('%RunMyMacros(date=',Date,')')
      into :calllist_Dates separated by ' '
      from DatesRange
      order by Date;
    quit; 
    
    
    &calllist;
    

    That's far easier to use, troubleshoot, and run, and doesn't require hardcoding the number of valid dates or anything else in it.