SAS Studio 3.8.
The subject line sounds odd, I know. But I couldn't figure out how to better summarize the issue.
I have code that returns the number of observations in a dataset via the macro var 'NOBS'. I am trying to wrap this code into a macro so I can efficiently send it different datasets. But the macro only works if I run the inner code of the macro by itself first. Here's what I mean:
The dataset sashelp.baseball has 322 obs, so I should see NOBS=322 when I run the following macro:
%macro count_me_obs(my_ds);
%let dsid = %sysfunc(open(&my_ds.));
%let nobs = %sysfunc(attrn(&dsid.,nlobs));
%let dsid = %sysfunc(close(&dsid.));
%mend count_me_obs;
%let getnobs = %count_me_obs(Sashelp.Baseball);
%put &=nobs;
/* Should return NOBS=322 */
Instead I get "WARNING: Apparent symbolic reference NOBS not resolved." as seen below.
1 OPTIONS NONOTES NOSTIMER NOSOURCE NOSYNTAXCHECK;
68
69 %macro count_me_obs(my_ds);
70 %let dsid = %sysfunc(open(&my_ds.));
71 %let nobs = %sysfunc(attrn(&dsid.,nlobs));
72 %let dsid = %sysfunc(close(&dsid.));
73 %mend count_me_obs;
74
75 %let getnobs = %count_me_obs(Sashelp.Baseball);
76 %put &=nobs;
WARNING: Apparent symbolic reference NOBS not resolved.
nobs
77
However, if I then run the following code (the guts of the macro):
%let dsid = %sysfunc(open(Sashelp.Baseball));
%let nobs = %sysfunc(attrn(&dsid.,nlobs));
%let dsid = %sysfunc(close(&dsid.));
%put &=nobs;
I correctly get this in my log:
1 OPTIONS NONOTES NOSTIMER NOSOURCE NOSYNTAXCHECK;
68
69 %let dsid = %sysfunc(open(Sashelp.Baseball));
70 %let nobs = %sysfunc(attrn(&dsid.,nlobs));
71 %let dsid = %sysfunc(close(&dsid.));
72 %put &=nobs;
NOBS=322
73
74
75 OPTIONS NONOTES NOSTIMER NOSOURCE NOSYNTAXCHECK;
NOW if I run the macro again, I correctly get the NOBS=322 in my log. Before you think that it's just putting in the previous value of NOBS, I can send other datasets to the macro and those get counted correctly now too, like so:
%macro count_me_obs(my_ds);
%let dsid = %sysfunc(open(&my_ds.));
%let nobs = %sysfunc(attrn(&dsid.,nlobs));
%let dsid = %sysfunc(close(&dsid.));
%mend count_me_obs;
%let getnobs = %count_me_obs(Sashelp.Baseball);
%put &=nobs;
/* NOBS=322 */
%let getnobs = %count_me_obs(Sashelp.Class);
%put &=nobs;
/* NOBS=19 */
%let getnobs = %count_me_obs(Sashelp.Gas);
%put &=nobs;
/* NOBS=171 */
%let getnobs = %count_me_obs(Sashelp.Springs);
%put &=nobs;
/* NOBS=1587 */
which gives me this log:
1 OPTIONS NONOTES NOSTIMER NOSOURCE NOSYNTAXCHECK;
68
69 %macro count_me_obs(my_ds);
70 %let dsid = %sysfunc(open(&my_ds.));
71 %let nobs = %sysfunc(attrn(&dsid.,nlobs));
72 %let dsid = %sysfunc(close(&dsid.));
73 %mend count_me_obs;
74
75 %let getnobs = %count_me_obs(Sashelp.Baseball);
76 %put &=nobs;
NOBS=322
77
78 %let getnobs = %count_me_obs(Sashelp.Class);
79 %put &=nobs;
NOBS=19
80
81 %let getnobs = %count_me_obs(Sashelp.Gas);
82 %put &=nobs;
NOBS=171
83
84 %let getnobs = %count_me_obs(Sashelp.Springs);
85 %put &=nobs;
NOBS=1587
86
So to recap: The macro doesn't work until I run the inner part of code by itself first. Then the whole macro works all day long. It's almost like I have to slap NOBS around first to wake it up, then it performs properly.
Of course, I need the code to work the first time it runs. Am I missing a step or something else? Thanks!
That's because the macro variable NOBS is first defined within your macro count_me_obs so is only local to that macro. Add a %GLOBAL statement to ensure NOBS is available outside your macro:
%macro count_me_obs(my_ds);
%global nobs;
%let dsid = %sysfunc(open(&my_ds.));
%let nobs = %sysfunc(attrn(&dsid.,nlobs));
%let dsid = %sysfunc(close(&dsid.));
%mend count_me_obs;
That's because the macro variable NOBS is first defined within your macro count_me_obs so is only local to that macro. Add a %GLOBAL statement to ensure NOBS is available outside your macro:
%macro count_me_obs(my_ds);
%global nobs;
%let dsid = %sysfunc(open(&my_ds.));
%let nobs = %sysfunc(attrn(&dsid.,nlobs));
%let dsid = %sysfunc(close(&dsid.));
%mend count_me_obs;
Thank you! That was indeed my issue. Solution worked like a charm and I'm a little more knowledgeable now for it 🙂
Keeping track of scope is very important when doing macro coding. Macro variables that should "result" from the macro must be defined as global (or you won't see them outside the macro), while variables used only for the functioning of the macro itself must be defined as local to avoid unwanted side effects.
Macro definitions, OTOH, exist side-by-side in the global scope (that's why defining a macro inside another makes no sense and only reduces code readability).
Another solution is to have the macro create text as its output, using the variable &NOBS, so that the assignment statement %LET GETNOBS is assigned a value:
%macro count_me_obs(my_ds);
%let dsid = %sysfunc(open(&my_ds.));
%let nobs = %sysfunc(attrn(&dsid.,nlobs));
%let dsid = %sysfunc(close(&dsid.));
&nobs
%mend count_me_obs;
%let getnobs = %count_me_obs(sashelp.baseball);
%put &=getnobs;
Available on demand!
Missed SAS Innovate Las Vegas? Watch all the action for free! View the keynotes, general sessions and 22 breakouts on demand.
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.