SAS Macro GLOBAL scope - global

is there a short way to make ALL macro variables created inside a macro global in scope?
ie:
%macro x;
%global _all_; * ??? ;
%let x=1;
%let y=1;
%let z=1;
%mend;

The only way I can think of to do this without having to declare each macro as global ahead of time and then do a %let statement is to use a macro in place of the %let statement.
In the code below, I create a macro called %mylet which has as its sole purpose the task of creating a global variable with the name and value I pass as arguments. I then use this macro in place of %let everywhere I want global variables defined.
eg.
%global myvar;
%let myvar=2;
would become...
%mylet(myvar,2);
/* Define a macro to declare variables as global */
%macro mylet(var,value);
%global &var;
%let &var.= &value ;
%mend;
/* Test macro */
%macro test;
%mylet(myvar,2);
%mylet(myvar2,12);
%mylet(myvar3,'string');
/* see that they are global inside the macro */
title "Macro scope inside test macro";
proc sql;
select *
from dictionary.macros
where name in('MYVAR','MYVAR2','MYVAR3');
quit;
%mend;
%test;
/* Check to see if they are still global outside the macro */
title "Macro scope outside test macro";
proc sql;
select *
from dictionary.macros
where name in('MYVAR','MYVAR2','MYVAR3');
quit;

If you create the macro variable within open code the macro variable will automatically be added to the global macro symbol table:
%let x=1;
%let y=1;
%let z=1;
This would be before you create the larger macro:
%macro x;
<code here>
%mend x;
Another alternative would be to create the variables using a data step:
data _null_;
set LIB.DSET;
x = 1;
call symput('x',x);
run;

If you create macro variables within a data-step, and that data-step is inside a macro, the macro variables created will be, by default, local in scope to that macro.
Use
call symputx('macvar',macval,'g');
to create global macro variables.

Related

Multiple values for a sas macro parameter

I'm new to SAS Macro programming and need to enable the following macro to be able to handle and process multiple values for its macro parameters.Hello,
data have;
input name $ ACCOUNT_ID $ cust_id;
cards;
ARTHUR CC1234 1234
TOM eil1235 1235
MIKEZ tb1236 1236
MATT mb1237 1237
LIZ TB1238 1238
PIZ VB1239 1239
TAN MB1240 1240
PANDA . 1241
;
run;
%MACRO algo (IN_DS=,VAR_LIST=,DATA_TYPE_LIST=,OUT_DS=);
DATA &OUT_DS;
SET &IN_DS;
%If &data_type_LIST = num %then
&var_LIST=sum(&VAR_LIST,2);
%else &var_LIST=cats(&var_LIST,'re');;
run;
%mend;
%algo(IN_DS=HAVE,VAR_LIST=CUST_ID,DATA_TYPE_LIST=num,OUT_DS=out1);`
I now need to enable this macro to be able to pass multiple values for the macro parameters. Something like this :
%algo(IN_DS=HAVE,VAR_LIST='CUST_ID,ACCT_ID',DATA_TYPE_LIST='num,char',OUT_DS=out1);
Can someone help me enable this functionality in the macro code.
The parameter argument should be macro quoted with %STR() in the macro invocation.
Try
%algo
( IN_DS=HAVE
, VAR_LIST= %STR (CUST_ID, ACCT_ID)
, DATA_TYPE_LIST=num
, OUT_DS=out1
);
Macro quoting is different than DATA step quoting used for character literals.
Make sure the macro can handle multiple values. In general it is not a good idea to use comma as the delimiter in your list of values when calling a macro.
Usually space is the best delimiter since then you can use the macro value directly in the generated code. For example if your variables are all of the same type you can just use data step ARRAY.
%MACRO algo (IN_DS=,VAR_LIST=,DATA_TYPE_LIST=,OUT_DS=);
DATA &OUT_DS;
SET &IN_DS;
array list &var_list ;
do _n_=1 to dim(list);
%if &data_type_LIST = num %then %do ;
list(_n_)=sum(list(_n_),2);
%end;
%else %do;
list(_n_)=cats(list(_n_),'re');
%end;
end;
run;
%mend algo;
If your variables are NOT all of the same type then you need to generate a separate statement for each variable. In that case you can use a different delimiter if you want, like a pipe character, that is easier to use as delimiter in calls to macro functions like %scan().
%MACRO algo (IN_DS=,VAR_LIST=,DATA_TYPE_LIST=,OUT_DS=);
%local i var;
DATA &OUT_DS;
SET &IN_DS;
%do i=1 %to %sysfunc(countw(&var_list,|));
%let var=%scan(&var_list,&i,|);
%if %scan(&data_type_LIST,&i,|) = num %then %do ;
&var=sum(&var,2);
%end;
%else %do;
&var=cats(&var,'re');
%end;
%end;
run;
%mend algo;
%algo(IN_DS=HAVE,VAR_LIST=CUST_ID|ACCT_ID,DATA_TYPE_LIST=num|char,OUT_DS=out1);
If you want to pass a list of variables and then use that list in the code you posted above, my suggestion would be to treat the &var_list as a list, and use scan to determine how many variables there are, and then loop through the list and execute the code accordingly.

How to pass macro variable into macro variable name in SAS ?

%let val = ' run';
%macro rrun;
%put successfully run;
%mend;
%macro x;
%r%cmpres(&val.);
/* %rrun;*/
%mend;%x
I"m trying to pass macro variable into macro variable name in SAS. to run %rrun using %cmpres(&val.) passing value to macro name "rrun" I don't understand why this doesn't work. when compress &val is "run"
How can I solve this and still passing &val ?
Thanks,
Why do you have quotes in your macro variable?
166 %let val = ' run';
167 %put |%cmpres(&val)|;
|' run'|
If you want to dynamically generate the name of macro to call it works much easier if you first generate the name into a macro variable and then reference that macro variable. Otherwise you risk confusing the tokenizer.
%macro x;
%local mname;
%let mname=r%cmpres(&val);
%&mname;
%mend;
%x

SAS Error trying to loop through multiple datasets

I'm trying to run some code which will hopefully concatenate multiple months or years worth of data. I am trying to figure out when a field was populated with a value. I.e. there is field XYZ in my data set and it is populated with value A in November 2016. If I run my code from Jan - Dec I would like a new field populated with the date that SAS encounters a non-blank value in that field.
Here's my code:
options mprint symbolgen source mlogic merror syntaxcheck ;
%macro append_monthly(iStart_date=, iEnd_date=);
%local tmp_date i;
%let tmp_date = %sysfunc(intnx(month,&iStart_date,0,beginning)) ;
%do %while (&tmp_date le &iEnd_date);
%let i = %sysfunc(sum(&tmp_date),yymmn4.);
%put &i.;
%let tmp_date = %sysfunc(intnx(month,&tmp_date,1,beginning)) ;
libname note "my.qualifiers.fords.note&i." disp=shr;
data new ;
set note.file ;
%if ln_note_crbur_date_delinq ne '' %then spc_cmt_date = &i.;
run;
%end;
%mend;
%append_monthly(iStart_date=%sysfunc(mdy(5,1,2016)), iEnd_date=%sysfunc(mdy(10,1,2016)) );
LIBNAME _ALL_ CLEAR;
Here's a sample from log with errors :
SYMBOLGEN: Macro variable TMP_DATE resolves to 20606
SYMBOLGEN: Macro variable IEND_DATE resolves to 20728
MLOGIC(APPEND_MONTHLY): %DO %WHILE(&tmp_date le &iEnd_date) condition is TRUE; loop will iterate again.
MLOGIC(APPEND_MONTHLY): %LET (variable name is I)
SYMBOLGEN: Macro variable TMP_DATE resolves to 20606
MLOGIC(APPEND_MONTHLY): %PUT &i.
SYMBOLGEN: Macro variable I resolves to 1606
1606
MLOGIC(APPEND_MONTHLY): %LET (variable name is TMP_DATE)
SYMBOLGEN: Macro variable TMP_DATE resolves to 20606
MPRINT(APPEND_MONTHLY): spc_cmt_date = 1605 run;
SYMBOLGEN: Macro variable I resolves to 1606
MPRINT(APPEND_MONTHLY): libname note "my.qualifiers.fords.note1606" disp=shr;
ERROR: Unable to clear or re-assign the library NOTE because it is still in use.
ERROR: Error in the LIBNAME statement.
NOTE: The SAS System stopped processing this step because of errors.
WARNING: The data set WORK.NEW may be incomplete. When this step was stopped there were 0 observations and 622 variables.
WARNING: Data set WORK.NEW was not replaced because this step was stopped.
NOTE: The DATA statement used 0.01 CPU seconds and 49483K.
NOTE: The address space has used a maximum of 4292K below the line and 240388K above the line.
I can't figure out why this isn't working. Maybe this could work using Proc append.
Basically, I just want my output with a field that returns a date in the form of YYMM for when field ln_note_crbur_date_delinq was non-blank.
Any help would be greatly appreciated
I'd guess the reason for your error is that the handle is not being cleared on your source file before the next libname statement tries to re-assign.
An easy fix would be to use a different alias (libref) each time, as follows:
libname note&i "my.qualifiers.fords.note&i." disp=shr;
Then adjust your data step like so:
data new ;
set note&i..file ;
The next part appears to be confusion between macro logic and data step. Simply remove the % symbols as follows:
if ln_note_crbur_date_delinq ne '' then spc_cmt_date = &i.;
Finally, add a proc append before the %end as follows:
proc append base=work.final data=new; run;
If work.final does not exist, it will be created in the same format as new.
EDIT:
following discussion in comments, here is a revised approach:
%macro append_monthly(iStart_date=, iEnd_date=);
%local tmp_date i set_statement;
%let tmp_date = %sysfunc(intnx(month,&iStart_date,0,beginning)) ;
%do %while (&tmp_date le &iEnd_date);
%let i = %sysfunc(sum(&tmp_date),yymmn4.);
%let tmp_date = %sysfunc(intnx(month,&tmp_date,1,beginning)) ;
%let set_statement=&set_statement &i..file;
libname note&i "my.qualifiers.fords.note&i." disp=shr;
%end;
data new ;
set &set_statement;
if ln_note_crbur_date_delinq ne '' then spc_cmt_date = &i.;
run;
%mend;
%append_monthly(iStart_date=%sysfunc(mdy(5,1,2016)), iEnd_date=%sysfunc(mdy(10,1,2016)) );
LIBNAME _ALL_ CLEAR;

sas macros for incrementing date

My codes are:
libname " Cp/mydata"
options ;
%let yyyymmdd=20050210;
%let offset=0;
%let startrange=0;
%let endrange=0;
/* MACRO FOR INCREMENTING THE DATE */
%macro base(yyyymmdd=, offset=);
%local date x ds; /* declare macro variables with local scope */
%let date=%sysfunc(mdy(%substr(&yyyymmdd,5,2)
,%substr(&yyyymmdd,7,2)
,%substr(&yyyymmdd,1,4))); /* convert yyyymmdd to SAS date */
%let loopout=100;/* hardcoded - number of times to check whether ds exists */
%do x=&offset %to &loopout; /* begin loop */
/* convert &date to yyyymmdd format */
%let ds=AQ.CO_%sysfunc(intnx(day,&date,&offset),yymmddn8.);
%if %sysfunc(exist( &ds )) %then %do;
%put &ds exists!;
&ds /* write out the dataset, if it exists */
%let x=&loopout; /* exit loop */
%end;
%else %do;
%put &ds does not exist - checking subsequent day;
%let date=&date+1;
%end;
%end;
%mend;
%macro loop(yyyymmdd=, startrange=, endrange=);
%local date x ds;
%let date=%sysfunc(mdy(%substr(&yyyymmdd,5,2)
,%substr(&yyyymmdd,7,2)
,%substr(&yyyymmdd,1,4)));
data x;
set set %base(yyyymmdd=&yyyymmdd, offset=0)
/* loop through each specific dataset, checking first whether it exists.. */
%do x=&startrange %to &endrange;
%let ds=AQ.CO_%sysfunc(intnx(day,&date,&x),yymmddn8.);
%if %sysfunc(exist( &ds )) %then %do;
&ds
%end;
%end;
;
run;
%mend;
This was the error generated when I tried to run this macro.
data temp;
58 set %loop(yyyymmdd=&yyyymmdd, startrange=&startrange,
58 ! endrange=&endrange);
ERROR: File WORK.DATA.DATA does not exist.
ERROR: File WORK.X.DATA does not exist.
AQ.CO_20050210 does not exist - checking subsequent day
AQ.CO_20050211 does not exist - checking subsequent day
AQ.CO_20050212 exists!
NOTE: The system stopped processing this step because of errors.
I want help on two things:
1) Here, I'm trying to increment my date by 1 or 2 or so on if that date is not there in my original dataset. Please help to make this macro work fine.
2)I would like to have another column ie work.date in my data that will have 0 or 1(1 if the specified date yyyymmdd exist in our original data and 0 if I'm incrementing). Please make the specified changes in my macro.
Thanks in advance!!
I wasn't quite sure exactly what your %base() macro was trying to achieve but there were a couple of things I noticed.
First try turning on option mprint; to help with debugging. If you still need more debugging info you can also try turning on the following options (I'd suggest 1 at a time until you know which ones you need):
option symbolgen macrogen mlogic;
Secondly, you have set set instead of just set in your example code. I don't think that is helping any =).
When I tried the code quickly on my machine I noticed I was getting a strange error (different from yours) when I called the %base() macro. It seemed like an error that shouldn't be occurring so I wrapped the call in an %unquote() function just to make sure and I started to receive the error your post mentioned. You may want to try this as well:
set %unquote(%base(yyyymmdd=&yyyymmdd, offset=0))
Normally the %unquote() function isn't required unless you are explicitly using macro quoting functions and getting strange errors, but SAS macros sometimes seem to have a mind of their own. I only ever add this when I know it is required.
Also, your libname call is missing a semicolon at the end of the line.
Finally, some advice on working with dates in the SAS macro language. Don't keep converting between the date value, and the formatted value. It will make your code bigger, more error prone and more difficult to read. I know because I used to do it that way too. Try instead to always work with variables that contain the actual date value (by using the result from %sysfunc(mdy()) ) and then if you need a formatted value then create a new variable (eg. %let yyyymmdd = %sysfunc(putn(&mydate),yymmddn8.);. When you pass values from one macro to another, don't pass the formatted values even if it seems easier, pass the actual values.
Making the above changes removed all errors on my machine. Final code:
libname " Cp/mydata";
%let yyyymmdd=20050210;
%let offset=0;
%let startrange=0;
%let endrange=0;
/* MACRO FOR INCREMENTING THE DATE */
%macro base(yyyymmdd=, offset=);
%local date x ds; /* declare macro variables with local scope */
%let date=%sysfunc(mdy(%substr(&yyyymmdd,5,2)
,%substr(&yyyymmdd,7,2)
,%substr(&yyyymmdd,1,4))); /* convert yyyymmdd to SAS date */
%let loopout=100;/* hardcoded - number of times to check whether ds exists */
%do x=&offset %to &loopout; /* begin loop */
/* convert &date to yyyymmdd format */
%let ds=AQ.CO_%sysfunc(intnx(day,&date,&offset),yymmddn8.);
%if %sysfunc(exist( &ds )) %then %do;
%put &ds exists!;
&ds /* write out the dataset, if it exists */
%let x=&loopout; /* exit loop */
%end;
%else %do;
%put &ds does not exist - checking subsequent day;
%let date=&date+1;
%end;
%end;
%mend;
%macro loop(yyyymmdd=, startrange=, endrange=);
%local date x ds;
%let date=%sysfunc(mdy(%substr(&yyyymmdd,5,2)
,%substr(&yyyymmdd,7,2)
,%substr(&yyyymmdd,1,4)));
data x;
set %unquote( %base(yyyymmdd=&yyyymmdd, offset=0))
/* loop through each specific dataset, checking first whether it exists.. */
%do x=&startrange %to &endrange;
%let ds=AQ.CO_%sysfunc(intnx(day,&date,&x),yymmddn8.);
%if %sysfunc(exist( &ds )) %then %do;
&ds
%end;
%end;
;
run;
%mend;
%loop(yyyymmdd=&yyyymmdd, startrange=&startrange, endrange=&endrange);
Seems to me that your solution is quite complex.
But i believe that at least one issue is the variable x in our second macro (%loop): i do not see where you define it.
You can probably do all of this much easier, IF you do not need to limit the loopout. If you just want all datasets beyond the offset, you can simplify all this by making use of the SASHELP library to find the datasets you need. And then just loop over that result.
DEPRECATED REPLY BELOW, misread the need
You are partially reinventing the wheel, have a deeper look at the intnx and intck functions.
http://support.sas.com/documentation/cdl/en/etsug/60372/HTML/default/viewer.htm#etsug_tsdata_sect038.htm
https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212700.htm

Attribute Change of Macro Variable in SAS

I have a macro variable like &a having value of (1234.45)*. I am trying to replace the ( and ) from the macro and replace them with a negative mark since its a negative number.
%let a=(1234.45)
Some of the options which I have appplied are
%macro test1;
%if %substr(&a,1,1) = '(' %then %do;
%let b=%substr(&a,1,'-')
%end;
%mend;
%test1
This is numeric conversion and best handled in a data step. If for some reason you really need a macro variable, use SYMPUT.
%let a=(1234.45);
data _null_;
x=input("&a.",comma10.);
call symputx("b",x);
run;
%put &=a &=b;