BookmarkSubscribeRSS Feed
☑ This topic is solved. Need further help from the community? Please sign in and ask a new question.
Michelle_
Fluorite | Level 6

Hi all, seeking help on how to write a macro to call another macro for different period.
I have different setting to run each month. Im wondering can I write a macro to auto repeat run the same macro. 

 

%macro repeat(n);
	%do i = 1 %to &n.;
		%if &i. = 1 %then 
			%do;
				%let month 	= 4;
				%let lag 	= 2;
			%end;
		%if &i. = 2 %then 
			%do;
				%let month 	= 7;
				%let lag 	= 2;
			%end;
		%if &i. = 3 %then 
			%do;
				%let month 	= 4;
				%let lag 	= 1;
			%end;
%month_setting; %setting; %end; %mend; %repeat(3); %macro month_setting; %global begin end; %if %sysevalf(&lag = 2) %then %do; %let begin = "%sysfunc(intnx(month,%sysfunc(inputN(&period.01,yymmdd10.)),-&month.),date9.)"d; %let end = "%sysfunc(intnx(month,%sysfunc(inputN(&period.01,yymmdd10.)), +&lag.),date9.)"d; %end; %else %if %sysevalf(&lag = 1) %then %do; %let begin = "%sysfunc(intnx(month,%sysfunc(inputN(&period.01,yymmdd10.)),-&month.+1),date9.)"d; %let end = "%sysfunc(intnx(month,%sysfunc(inputN(&period.01,yymmdd10.)), +&lag.+1),date9.)"d; %end; %let end_YM = %sysfunc(inputn(%sysfunc(substr(%sysfunc(putn(&end., yymmddn8.)),1, 6)), 6.)); %let beg_YM = %sysfunc(inputn(%sysfunc(substr(%sysfunc(putn(&begin., yymmddn8.)),1, 6)), 6.)); %mend;
/*macro sample*/ %macro setting; Data working.M&Month._Setting; month = &month.; lag = &lag.; begin = put(&begin.,yymmddn8.); end = put(&end.,yymmddn8.); run; %mend;

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
Kurt_Bremser
Super User

Sloppy macro programming. Always (as in ALWAYS) define variables used in the macro as local (%LOCAL statement).

Your macro datecreate uses i for the loop, which is also used in repeat; after returning from datecreate, &i will have a large enough value to terminate the loop in repeat.

View solution in original post

7 REPLIES 7
Tom
Super User Tom
Super User

Did you try the code?  Did it work?  If not what was wrong?

 

You can call another macro inside a macro.

 

But you have to have defined it before you try to CALL it.

 

So make sure to move the call to %REPEAT() to after the definition of all three macros.

Michelle_
Fluorite | Level 6

No error pop up. 
It only able to run one times.  I have write %repeat(3). But, it only able to run &i.= 1

Michelle_
Fluorite | Level 6

Apologies. Can ignore the previous coding i asked. The issue seems like appear after the %datecreate macro. 
If only run until %setting then it works. However, when run one more macro (i.e. %datecreate) then it won't auto generate the second and third run. 
So, i have create delete specific global macro. 

 

%macro repeat(n);
	%do i = 1 %to &n.;
		%if &i. = 1 %then 
			%do;
				%let month 	= 4;
				%let lag 	= 2;
			%end;
		%if &i. = 2 %then 
			%do;
				%let month 	= 7;
				%let lag 	= 2;
			%end;
		%if &i. = 3 %then 
			%do;
				%let month 	= 4;
				%let lag 	= 1;
			%end;

		%month_setting;
		%setting;
		%datecreate;
		%deleteALL;
	%end;
%mend;
%repeat(3); 

%macro month_setting;
    %global begin end;
	%if %sysevalf(&lag = 2) %then
		%do;
			%let begin 		= "%sysfunc(intnx(month,%sysfunc(inputN(&period.01,yymmdd10.)),-&month.),date9.)"d;
			%let end 		= "%sysfunc(intnx(month,%sysfunc(inputN(&period.01,yymmdd10.)), +&lag.),date9.)"d;
		%end;
	%else %if %sysevalf(&lag = 1) %then 
		%do;
			%let begin 		= "%sysfunc(intnx(month,%sysfunc(inputN(&period.01,yymmdd10.)),-&month.+1),date9.)"d;
			%let end 		= "%sysfunc(intnx(month,%sysfunc(inputN(&period.01,yymmdd10.)), +&lag.+1),date9.)"d;
		%end;		
	%let end_YM 	= %sysfunc(inputn(%sysfunc(substr(%sysfunc(putn(&end., yymmddn8.)),1, 6)), 6.));
	%let beg_YM 	= %sysfunc(inputn(%sysfunc(substr(%sysfunc(putn(&begin., yymmddn8.)),1, 6)), 6.));

%mend;

/*macro sample*/
%macro setting;
	Data working.M&Month._Setting;
		month 	= &month.;
		lag 	= &lag.;
		begin	= put(&begin.,yymmddn8.);
		end 	= put(&end.,yymmddn8.);
	run;
%mend;


%macro datecreate;
	%global dif;
	%let dif = %sysfunc(intck(MONTH,&begin,&end)); 
		%do i= 1 %to &dif;
			%global date_&i;

			%let date_&i=%sysfunc(intnx(month,&begin,&i,B),yymmn6.);
			%put &&date_&i;
		%end;
%mend;


%macro deleteALL;
 
   	options nonotes;

  	%local vars;
 
  	proc sql noprint;
      	    select name into: vars separated by ' '
         	  from dictionary.macros
            	      where scope = 'GLOBAL' and (name contains 'DATE_' or name in ('MONTH', 'LAG', 'BEGIN','END', 'BEG_YM', 'END_YM', 'DIF'));
   	quit;

   	%symdel &vars;
 
   	options notes;
 
    	%put NOTE: Macro variables deleted.;
 
%mend;

But, the above coding not working also. It didn't pop any errors. However, it will not generate the results for Month 7  + Lag 2 and Month 4 + lag 1. 

Kurt_Bremser
Super User

Sloppy macro programming. Always (as in ALWAYS) define variables used in the macro as local (%LOCAL statement).

Your macro datecreate uses i for the loop, which is also used in repeat; after returning from datecreate, &i will have a large enough value to terminate the loop in repeat.

Quentin
Super User

Kurt has identified the problem, I want to share an example.  Suppose you have macros OUTER and INNER where OUTER invokes INNER.  You want to have OUTER loop 2 times, and INNER loop 3 times per iteration of OUTER. 

 

If you code it like:

%macro outer(n=) ;
  %do i=1 %to &n ;
    %put %nrstr(%%)&sysmacroname ;
    %put _user_ ;
    %put ;

    %inner(n=3) ;

  %end ;
%mend outer ;

%macro inner(n=) ;
  %do i=1 %to &n ;  
    %put %nrstr(%%)&sysmacroname ;
    %put _user_ ;
    %put ;
  %end ;
%mend inner ;

%outer(n=2)

Your log will show that OUTER is loops once, not twice as intended.  This is the problem you're having:

22   %outer(n=2)
%OUTER
OUTER I 1
OUTER N 2

%INNER
INNER N 3
OUTER I 1
OUTER N 2

%INNER
INNER N 3
OUTER I 2
OUTER N 2

%INNER
INNER N 3
OUTER I 3
OUTER N 2

When OUTER executes, it creates the macro variables i and N.  

When INNER executes, it creates a new macro variable N which is a different macro variable than the variable N which exists in outer.  It does this because N is defined as macro parameter to INNER.

But when the %DO loop in INNER executes, it does NOT create a new macro variable i for INNER.  Instead it uses the macro variable i that already exists in OUTER. So this is a naming collision.  INNER has changed the values of a macro variable that belongs to OUTER.  And because INNER loops three times, you end up with the macro variable i having the value of 3.  Then the %DO loop in OUTER does not loop a second time, because i is already 3.

 

The way to avoid this problem is to declare the macro variable i to be local in INNER.  That way when INNER executes, the %LOCAL statement will create a new local macro variable i for INNER that is separate from the macro variable i in OUTER.

 

%macro outer(n=) ;
  %local i ;
  %do i=1 %to &n ;
    %put %nrstr(%%)&sysmacroname ;
    %put _user_ ;
    %put ;

    %inner(n=3) ;

  %end ;
%mend outer ;

%macro inner(n=) ;
  %local i;
  %do i=1 %to &n ;  
    %put %nrstr(%%)&sysmacroname ;
    %put _user_ ;
    %put ;
  %end ;
%mend inner ;

%outer(n=2)

Log:

22   %outer(n=2)
%OUTER
OUTER I 1
OUTER N 2

%INNER
INNER I 1
INNER N 3
OUTER I 1
OUTER N 2

%INNER
INNER I 2
INNER N 3
OUTER I 1
OUTER N 2

%INNER
INNER I 3
INNER N 3
OUTER I 1
OUTER N 2

%OUTER
OUTER I 2
OUTER N 2

%INNER
INNER I 1
INNER N 3
OUTER I 2
OUTER N 2

%INNER
INNER I 2
INNER N 3
OUTER I 2
OUTER N 2

%INNER
INNER I 3
INNER N 3
OUTER I 2
OUTER N 2

With that, outer loops two times, so you get the expected 2*3=6 executions of INNER.  And you can see in the log that the macro variables i and N in INNER are independent of the macro variables i and N in OUTER.

 

yabwon
Amethyst | Level 16

you can do it much simpler and much more readable using data step:

 

libname working (work);

%macro setting(period, i);

data working.P&period._Setting;

  period = input(cats(symget('period'),'01'),yymmdd8.);
  i = symgetn("i");

  select(i);
    when(1) 
      do;
        month = 4;
        lag   = 2;
      end;
    when(2) 
      do;
        month = 7;
        lag   = 2;
      end;
    when(3) 
      do;
        month = 4;
        lag   = 2;
      end;
    otherwise put "ERROR: I should be in 1, 2, 3.";
  end;

  select(lag);
    when(2)
      do;
        b = intnx("month", period,-month);
        e = intnx("month", period, +lag);
      end;
    when(1)
      do;
        b = intnx("month", period,-month+1);
        e = intnx("month", period, +lag+1);
      end;
    otherwise put "ERROR: LAG shoild be  in 1, 2.";
  end; 

  begin	= put(b,yymmddn8.);
  end 	= put(e,yymmddn8.);


  /* you didnot used those 2 in the code at all */
	beg_YM 	= put(b,yymmn6.);
  end_YM 	= put(e,yymmn6.);




  keep month lag begin end beg_YM end_YM ;
run;

%mend;



%setting(202308, 1)
proc print;
run;

%setting(202308, 2)
proc print;
run;

%setting(202308, 3)
proc print;
run;

 

Bart

_______________
Polish SAS Users Group: www.polsug.com and communities.sas.com/polsug

"SAS Packages: the way to share" at SGF2020 Proceedings (the latest version), GitHub Repository, and YouTube Video.
Hands-on-Workshop: "Share your code with SAS Packages"
"My First SAS Package: A How-To" at SGF2021 Proceedings

SAS Ballot Ideas: one: SPF in SAS, two, and three
SAS Documentation



hackathon24-white-horiz.png

2025 SAS Hackathon: There is still time!

Good news: We've extended SAS Hackathon registration until Sept. 12, so you still have time to be part of our biggest event yet – our five-year anniversary!

Register Now

Creating Custom Steps in SAS Studio

Check out this tutorial series to learn how to build your own steps in SAS Studio.

Find more tutorials on the SAS Users YouTube channel.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 7 replies
  • 1842 views
  • 6 likes
  • 5 in conversation