SAS macro execution order - sas

i use a macro to lock a Dataset:
%macro lockTab(member=APP_DATABASE,timeout=30,retry=500);
%global LOCK_&member;
%let LOCK_&member = ;
%local starttime;
%let starttime = %sysfunc(datetime());
%put try to lock &member.: &starttime;
%do %until (&syslckrc = 0
or %sysevalf(%sysfunc(datetime()) > (&starttime + &timeout)));
lock APPLIB.&member.;
%put syslckrc=&syslckrc;
%if &syslckrc > 0 %then %let rc=%sysfunc(sleep(&retry.));
%end;
%let endtime = %sysfunc(datetime());
%put end of try to lock &member.: &endtime;
%if &syslckrc <= 0 %then %do;
%let LOCK_&member = LOCK;
%end;
%else %do;
%let _appRetcode = 12;
%let _appErrtext = Database is locked.;
%end;
%put ende Locktab: appretcode: &_appRetcode;
%put ende Locktab: syslckrc: &syslckrc;
%mend;
This is my code using the macro (_appRetcode is a global variable),
macro spinner is just a datastep to Show a loading spinner on the web page.
Then lock the dataset and put out _appRetcode which is set in lockTab.
%debug prints out all macro variables with scope.
%spinner(show)
%lockTab
%put EYECATCHER &_appRetcode;
%debug(DUMP Variablen nach LOCKTAB)
and this is my SASlog:
NOTE: 1 record was written to the file _WEBOUT.
The minimum record length was 42.
The maximum record length was 42.
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
SYMBOLGEN: Macro variable _APPRETCODE resolves to 0
EYECATCHER 0
SYMBOLGEN: Macro variable MEMBER resolves to APP_DATABASE
SYMBOLGEN: Macro variable MEMBER resolves to APP_DATABASE
SYMBOLGEN: Macro variable MEMBER resolves to APP_DATABASE
The SAS System
SYMBOLGEN: Macro variable STARTTIME resolves to 1834989255.00056
try to lock APP_DATABASE: 1834989255.00056
SYMBOLGEN: Macro variable MEMBER resolves to APP_DATABASE
ERROR: A lock is not available for APPLIB.APP_DATABASE.DATA.
ERROR: Lock held by process 1360575.
SYMBOLGEN: Macro variable SYSLCKRC resolves to 70031
syslckrc=70031
SYMBOLGEN: Macro variable SYSLCKRC resolves to 70031
SYMBOLGEN: Macro variable RETRY resolves to 500
SYMBOLGEN: Macro variable SYSLCKRC resolves to 70031
SYMBOLGEN: Macro variable STARTTIME resolves to 1834989255.00056
SYMBOLGEN: Macro variable TIMEOUT resolves to 30
SYMBOLGEN: Macro variable MEMBER resolves to APP_DATABASE
ERROR: A lock is not available for APPLIB.APP_DATABASE.DATA.
ERROR: Lock held by process 1360575.
.......
end of try to lock APP_DATABASE: 1834989285.01467
SYMBOLGEN: Macro variable SYSLCKRC resolves to 70031
SYMBOLGEN: Macro variable _APPRETCODE resolves to 12
ende Locktab: appretcode: 12
The SAS System
SYMBOLGEN: Macro variable SYSLCKRC resolves to 70031
ende Locktab: syslckrc: 70031
Why is EYECATCHER printed out before the Output of my macro?
I expected first output of macro lockTab, and then "EYECATCHER 12".
?????
%debug gives: (scope/variable/value) GLOBAL / _APPRETCODE / 12

I think the issue has to do with the parser deciding when a macro invocation has ended. If a macro is defined with parameters (even a null list of parameters), then the macro call does not end at white space. It ends at the closing parenthesis. It will also end at a SAS language token (including a SAS language semicolon), or another macro invocation.
That is why something ugly like below will work:
%macro doit (x=) ;
%put &=x ;
%mend doit;
%doit (x=3)
Changing #Allan's example, if demo1 and demo2 are defined without parameters, the code works, because the invocation of demo1 and demo3 will be triggered by white space.
358 %macro demo1; %put 1 first; %mend;
359 %macro demo3; %put 3 third; %mend;
360 %macro x;
361 %demo1
362 %put 2 second;
363 %demo3
364 %mend x;
365 %x
1 first
2 second
3 third
But if %demo1 is defined with a parameter list, the white space is not enough to end the macro call. And even the %PUT statement doesn't end the macro call. SAS keeps looking for a parameter list until it hits the call the %demo3, and realizes "well, that first macro call must be done." By then the %PUT statement has already executed.
366 %macro demo1(); %put 1 first; %mend;
367 %macro demo3; %put 3 third; %mend;
368 %macro x;
369 %demo1
370 %put 2 second;
371 %demo3
372 %mend x;
373 %x
2 second
1 first
3 third
As evidence, consider below. It looks like I'm passing the VAR parameter to %X (which has no parameters defined). But the invocation of %demo1 is still happily waiting for a parameter list, and it accepts it. This shows that the %PUT statement was not enough to end the invocation of %demo1.
404 %macro demo1(var=); %put 1 first; %put &=var ;%mend;
405 %macro x;
406 %demo1
407 %put 2 second;
408 %mend x;
409 %x(var=hello)
2 second
1 first
VAR=hello
I have a hard time remembering all the rules that Ian Whitlock taught me, and sometimes fear attributing one to him which I am remembering incorrectly. But I'm pretty confident he believed all macros should have at least one parameter (his definition for a macro was 'a parameterized unit of code'), and that macro calls should always end with parentheses, even if it was a null list of parameters to accept the defaultes, i.e. %doit(). That ensures that the macro executes when expected, without adding a SAS language semicolon which can sometimes muck things up.

Just an idea:
Have you tried putting semicolons after your macro calls? Missing semicolons usually poses some problems. Even though the macros may seem to run ok.

Is the code you provided, wrapped within a parent macro?
I was able to reproduce this behaviour, but ONLY within another macro.
Sample code:
%macro x;
%macro demo1(); %put see; %mend;
%macro demo2(); %put this; %mend;
%demo1 %put here; %demo2()
%mend;
%x
which prints:
here
see
this
Contrasting with:
%macro demo1(); %put see; %mend;
%macro demo2(); %put this; %mend;
%demo1 %put here; %demo2()
Which prints:
see
here
this
This does indeed appear to be a parser bug, or at least - an inconsistency.
I suggest moving the child macro definitions outside the parent wrapper. I also recommend always defining macros with brackets, and invoking them as such.

Related

PROC SQL "SELECT INTO" does not create macro variable

I've run into an issue creating macro variables using proc sql select into : logic. The code seems to be pretty straightforward, but I should note that the macro that is causing problems is called from several other macros.
Here is the snippet that is causing issues.
%do year_num=1 %to 5;
%if &year_num=1 %then %let min_date = %eval(&max2.-17);
%else %let min_date = %eval(&min_date.-12);
data tmp;
set bf(firstobs=&min_date obs=%eval(11+&min_date));
run;
data tmp2;
set bf(firstobs=%eval(5+&min_date) obs=%eval(7+&min_date));
run;
proc sql noprint;
select sum(EP), sum(ExpectedLoss)
into :totep, :totexpt
from tmp;
select sum(EP), sum(ExpectedLoss)
into :partep, :partexpt
from tmp2;
quit;
%put _LOCAL_;
*Other code...;
%end;
For some reason, the variables totep, totexpt, partep and pqrtexpt are not getting created and I can't find any useful information in the log that may shed light on the situation.
Here is part of the log, including the output from _LOCAL_.
SYMBOLGEN: Macro variable YEAR_NUM resolves to 1
SYMBOLGEN: Macro variable MAX2 resolves to 96
MPRINT(BFMETHOD): data tmp;
SYMBOLGEN: Macro variable MIN_DATE resolves to 79
SYMBOLGEN: Macro variable MIN_DATE resolves to 79
MPRINT(BFMETHOD): set bf(firstobs=79 obs=90);
MPRINT(BFMETHOD): run;
MPRINT(BFMETHOD): data tmp2;
SYMBOLGEN: Macro variable MIN_DATE resolves to 79
SYMBOLGEN: Macro variable MIN_DATE resolves to 79
MPRINT(BFMETHOD): set bf(firstobs=84 obs=86);
MPRINT(BFMETHOD): run;
MPRINT(BFMETHOD): proc sql noprint;
MPRINT(BFMETHOD): select sum(EP), sum(ExpectedLoss) into :totep, :totexpt from tmp;
MPRINT(BFMETHOD): select sum(EP), sum(ExpectedLoss) into :partep, :partexpt from tmp2;
MPRINT(BFMETHOD): quit;
BFMETHOD I 12
BFMETHOD DSET all
BFMETHOD SEAS_MIN 0.6
BFMETHOD YEAR_NUM 1
BFMETHOD SEAS_MAX 1.66666666666666
BFMETHOD MIN_DATE 79
data tmp; set bf(firstobs=79 obs=90); run;
NOTE: There were 12 observations read from the data set WORK.BF.
NOTE: The data set WORK.TMP has 12 observations and 35 variables.
NOTE: Compressing data set WORK.TMP increased size by 100.00 percent.
Compressed is 2 pages; un-compressed would require 1 pages.
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
109 + data tmp2; set bf(firstobs=84 obs=86); run;
NOTE: There were 3 observations read from the data set WORK.BF.
NOTE: The data set WORK.TMP2 has 3 observations and 35 variables.
NOTE: Compressing data set WORK.TMP2 increased size by 100.00 percent.
Compressed is 2 pages; un-compressed would require 1 pages.
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
109 + proc sql noprint;
109 + select sum(EP), sum(ExpectedLoss) into :totep, :totexpt from tmp;
109 +
select sum(EP), sum(ExpectedLoss) into :partep, :partexpt from tmp2;
109 +
quit;
NOTE: PROCEDURE SQL used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
If I remove noprint from the proc sql statement then the correct values are output so I'm not sure what's going on. Any help would be appreciated.
Your issue is almost certainly related to execution timing. Particularly when using call execute, you can easily run into problems with timing where the macro variable is resolved by the SAS processor earlier than it's given a useful value.
Here's a simplified example. I specifically make a dataset so you can see when that happens (it's not a necessary step nor does it affect the example, it just makes a useful log entry).
Notice how when the macro runs on its own, everything works how you'd expect; but when it's run with call execute the %put executes before the macro actually executes.
The third example uses %nrstr to force SAS to not try to resolve the macro before actually running it - which causes it to be submitted properly.
Effectively, the first call execute version has SAS process the macro text then submit it to sas.exe - which you do not really want. Adding %nrstr fixes that.
%macro do_something();
%local mlist;
data class_m;
set sashelp.class;
where sex='M';
run;
proc sql;
select name into :mlist separated by ' '
from class_m;
quit;
%put &=mlist;
%mend do_something;
%put Macro run on its own;
%do_something;
%put Macro run via call execute;
options mprint symbolgen;
data _null_;
set sashelp.class;
if _n_=1 then call execute('%do_something()');
stop;
run;
%put Macro run with nrstr and call execute;
data _null_;
set sashelp.class;
if _n_=1 then call execute('%nrstr(%do_something())');
stop;
run;

TYPES OF GLOBAL MACRO VARIABLES IN SAS

Can we call GLOBAL MACRO variables which is created by
% let,
% do ,
callsymput,
sql into clause ,
macro parameters,
please tell me.
For the sake of discussion the highest scope is the global scope
The macro variable utilized at time of ampersand or superq resolution is the current or nearest higher scope. If you require a same named macro variable value from a scope higher than the default you must query it from dictionary table (SASHELP.VMACRO)
When you plan on populating a global macro variable from code executed within a macro invocation, declare the macro variable using the %GLOBAL statement;
The depth of a macro's execution can be known using the macro function %SYSMEXECDEPTH. There are numerous other macro functions for 'reflection' of the macro system, mostly the SYS* and SYM* functions found in SAS documentation.
An example. The zoot macro populates global macro variable age, even when zoot is invoked in a macro execution depth.
options nocenter;
%let MY_MACRO_VAR = global;
%macro foo;
%local MY_MACRO_VAR;
%let MY_MACRO_VAR = local_1;
%put &=MY_MACRO_VAR;
options nolabel;
title "&SYSMACRONAME %nrstr(%SYSMEXECDEPTH=)%SYSMEXECDEPTH";
proc sql;
select * from dictionary.macros
where name = 'MY_MACRO_VAR';
quit;
options label;
%bar;
%mend;
%macro bar;
%local MY_MACRO_VAR;
%let MY_MACRO_VAR = local_%SYSMEXECDEPTH;
%put &=MY_MACRO_VAR;
options nolabel;
title "&SYSMACRONAME %nrstr(%SYSMEXECDEPTH=)%SYSMEXECDEPTH";
proc sql;
select * from dictionary.macros
where name = 'MY_MACRO_VAR';
quit;
options label;
%put &SYSMACRONAME %nrstr(%SYSMEXECDEPTH=)%SYSMEXECDEPTH;
%put _user_;
%if %SYSMEXECDEPTH=2 %then %bar;
%zoot
%mend;
%macro zoot;
%global my_global;
proc sql noprint;
select age into :my_global from sashelp.class where name = 'Jane';
quit;
%mend;
%foo;
%put _user_;
The scope of other interactions with the macro system such as EXECUTE, SYMGET or RESOLVE can become rather subtle.

sas macro loop to rename variable

Hi I am trying to rename variables using SAS Macro loop.
%Let t1=12Mth;
%Let t2=20;
%Let t3=30;
%Let t4=40;
%Let t5=50;
%Let t6=60;
%macro Re(time);
%Do I = 1 %to &time.;
data MilkNew;
set Milk;
rename MT&&t&I..Sp=MTSp&&t&I.;
run;
%end;
%mend Re;
%Re(6)
This loop is mean to rename MT...Sp to MTSp.... Eg:MT20SP to MTSp20.
When I run my loop, there was no error but the variable names were not changed in MilkNew at all.
Where does the problem come? Thanks!
If the only purpose of the macro is to rename the variables in the data set, then why read the data with a set statement. Your data set is probably really small so you don't even realize the inefficiency of doing that. Instead use the modify statement in proc datasets to accomplish the same thing, but more efficiently. Here's an alternative macro for you.
%macro renamevar(dsname, time);
%local lib ds i;
%let lib = %sysfunc(coalescec(%scan(&dsname, -2, %str(.)), work));
%let ds = %scan(&dsname, -1, %str(.));
proc datasets lib=&lib nolist;
modify &ds;
rename
%do i = 1 %to &time;
mt&&t&i..Sp=MTSp&&t&i.
%end;
;
quit;
%mend;
%renamevar(milk, 6);
Here's the log after the macro call:
NOTE: Renaming variable mt12MthSp to MTSp12Mth.
NOTE: Renaming variable mt20Sp to MTSp20.
NOTE: Renaming variable mt30Sp to MTSp30.
NOTE: Renaming variable mt40Sp to MTSp40.
NOTE: Renaming variable mt50Sp to MTSp50.
NOTE: Renaming variable mt60Sp to MTSp60.
NOTE: MODIFY was successful for WORK.MILK.DATA.
NOTE: PROCEDURE DATASETS used (Total process time):
real time 0.00 seconds
cpu time 0.01 seconds
You should move the loop so that it only generates just the RENAME statement (or even just the old=new name pairs). What is happening now is that you keep overwriting MilkNew so only the last RENAME has any effect.
%macro Re(time);
data MilkNew;
set Milk;
%do I = 1 %to &time.;
rename MT&&t&I..Sp=MTSp&&t&I.;
%end;
run;
%mend Re;
%Re(6)
You should have seen the last variable name in the loop (so the 6th) changed. That's because you repeated the same data step with the same source dataset but a different destination - so each time you 'forgot' the changes made in the earlier step.
So, this would've worked, though I'll get in a minute to why this isn't a good way to do this.
%Let t1=12Mth;
%Let t2=20;
%Let t3=30;
%Let t4=40;
%Let t5=50;
%Let t6=60;
%macro Re(time);
%Do I = 1 %to &time.;
data Milk;
set Milk;
rename MT&&t&I..Sp=MTSp&&t&I.;
run;
%end;
%mend Re;
data milk;
input
MT12mthSP
MT20SP
MT30SP
MT40SP
MT50SP
MT60SP
;
datalines;
12 20 30 40 50 60
;;;;
run;
%Re(6)
Here I had it make all changes to Milk and save them back in that dataset. If you want to preserve Milk then first make Milk_New then have that in both set and data statements.
Second, you should not do a new data step for each change. Macros don't have to have a data step in them; they can be run inside the datastep.
So for example:
%macro Re(time);
%Do I = 1 %to &time.;
rename MT&&t&I..Sp=MTSp&&t&I.;
%end;
%mend Re;
data milk_new;
set milk;
%Re(6);
run;
Even better would be generating this list outside of a macro entirely - look up "generating code SAS" for suggestions on that.
If you didn't see any renames at all, you also may have an issue where a label is present on the column(s). That won't affect your usage of the variable name, but it will make it confusing. Use
label _all_;
Or include a label-clearing statement (label <varname>; where you pop in the same variable name as the original variable name before rename) inside your macro loop to fix that.

Echo SAS macro invocation to SAS log

Is there a system option or similar that will automatically echo macro invocations to the SAS log? When debugging code, I would like to see in the log every macro invocation, including what parameters were passed.
So that if I submit %Test(x=1) the log will show something like:
MACRO INVOKED: %TEST(x=1)
When calling a macro in open code, this is not an issue, because the macro call is shown in the usual log. But when outer macros call inner macros, the actual call to %inner is not shown by default. I tried turning on MLOGIC, MPRINT, etc, but couldn't find something that would show me the macro call itself. I think what I want is an MINVOCATION option.
Below I fake an MINVOCATION option by adding /parmbuff to macro definitions, but was hoping for a way to see macro calls without mucking with the macro definition.
%macro test(x=0,y=0,debug=0) /parmbuff ;
%if &debug %then %put MINVOCATION: %nrstr(%%)&sysmacroname&syspbuff ;
data _null_ ;
x=&x ;
y=&y ;
put x= y= ;
run ;
%mend test ;
%macro outer(debug=0) /parmbuff ;
%if &debug %then %put MINVOCATION: %nrstr(%%)&sysmacroname&syspbuff ;
%test(x=1,debug=&debug)
%test(x=1,y=2,debug=&debug)
%mend outer ;
options mprint mprintnest ;
%outer(debug=1)
Returns the desired:
908 options mprint mprintnest ;
909 %outer(debug=1)
MINVOCATION: %OUTER(debug=1)
MINVOCATION: %TEST(x=1,debug=1)
MPRINT(OUTER.TEST): data _null_ ;
MPRINT(OUTER.TEST): x=1 ;
MPRINT(OUTER.TEST): y=0 ;
MPRINT(OUTER.TEST): put x= y= ;
MPRINT(OUTER.TEST): run ;
x=1 y=0
MINVOCATION: %TEST(x=1,y=2,debug=1)
MPRINT(OUTER.TEST): data _null_ ;
MPRINT(OUTER.TEST): x=1 ;
MPRINT(OUTER.TEST): y=2 ;
MPRINT(OUTER.TEST): put x= y= ;
MPRINT(OUTER.TEST): run ;
x=1 y=2
I think you may be looking for option mlogic.
Example code:
option mprint mlogic ;
%macro y(blah);
%put &blah;
%mend;
%macro x();
%y(hello);
%put x;
%mend;
%x;
Gives:
MLOGIC(X): Beginning execution.
MLOGIC(Y): Beginning execution.
MLOGIC(Y): Parameter BLAH has value hello
MLOGIC(Y): %PUT &blah
hello
MLOGIC(Y): Ending execution.
MPRINT(X): ;
MLOGIC(X): %PUT x
x
MLOGIC(X): Ending execution.
You can see it tells you when the macro begins execution, which macro is executing, and also the value of any paramters passed in.
UPDATE
Based on your clarifications, this was the closest I could find. Basically you need to setup a libname for 'stored' macros. When you define your macro, add the options / store source to tell it to store the source code for the macro into the stored macro library:
libname mac "e:\temp";
option mstored sasmstore=mac;
%macro blah(something=whatever) / store source;
%put hi;
%mend;
You can later retrieve the source code by using the %copy macro (SAS v9+). This macro has options to write the source to a file rather than the log. You can then read in the file and extract the default parameter values yourself.
%COPY blah / source;
Gives:
%macro blah(something=whatever) / store source;
%put hi;
%mend;
This whitepaper goes into additional details.
It's a lot of extra steps I know but that seems to be a pretty unusual request.
You may be better off rethinking your strategy. For example, a far simpler method might simply be to define your defaults this way:
%macro hasDefaults(x=1,y=2);
%local default_x default_y;
%let default_x = 1;
%let default_y = 2;
%if &x ne &default_x %then %do;
%put The default for x was changed from &default_x to &x.;
%end;
%mend;
This is far from ideal as well, but you'll have to weigh up what will work better for your needs.
If you're willing to update all of your macros, which it sounds like you'd have to do anyways, then what about adding:
%put _local_;
at the top of each? At macro invocation, the only local macro variables defined will be those parameters, right?
%macro mymacro(x=,y=,z=);
%put _local_;
proc print data=sashelp.class;
run;
%mend mymacro;
%mymacro(x=1,y=2);
Gives a log of:
08 %mymacro(x=1,y=2);
MYMACRO X 1
MYMACRO Y 2
MYMACRO Z
NOTE: There were 19 observations read from the data set SASHELP.CLASS.
NOTE: PROCEDURE PRINT used (Total process time):
real time 0.05 seconds
cpu time 0.03 seconds
You could always also put the macro name in there:
%macro mymacro(x=,y=,z=);
%put MACRO INVOKED: &sysmacroname;
%put Parameters:;
%put _local_;
proc print data=sashelp.class;
run;
%mend mymacro;
%mymacro(x=1,y=2);
Though it's returned as part of the %put _local_ so it's probably extraneous.

How do I use a value that is returned from a Macro Variable in another macro

I have simplified this a lot so it can be repeated
%macro macro_one(dt2);
%let var1 = &dt2;
%mend;
Then I have another macro and I want to use the output from macro one in macro 2
%macro macro_print(dt2);
/*call macro 1*/
%macro_one(&dt2);
%put &var1;
%mend;
/call macro/
%macro_print('purple');
It should print purple in the logs but I get an error
I get an error though - i suspect I need to assign the macro variable from macro one when I call macro two.
First, I suspect you have a typo between your code and here. Proper way to define a macro is:
%macro blah(x);
<do stuff>
%mend;
not:
%macro_blah(x);
<do stuff>
%mend;
The macro is created in %macro_one and defaults to a local scope. You can fix this by declaring it %global.
%macro macro_one(dt2);
%global var1;
%let var1=&dt2;
%mend;
Also, use %put not put in %macro_two.