SAS indirect macro processing [duplicate] - sas

This question already has an answer here:
sas MACRO ampersand
(1 answer)
Closed 8 years ago.
I have a question on an exam paper which asks what is read in the SAS log
%let test=one;
%let one=two;
%let two=three;
%let three=last;
%put what displays is &&&&&test;
I was very surprised to find the answer was: two as I would have thought that this reference would fully resolve to last. SAS also agrees with the answer to be two.
Can anyone please explain how SAS arrives at the answer two as all theory notes I have read suggests that the macro processor should do the following
scan1 &&&&&test - > &&&&test ( i.e && resolves to & and tells processor to continue to scan from right to left)
scan2 &&&&test - > &&&one
scan3 &&&one - > &&two
scan4 &&two - > &three
scan5 &three - > last

Using the symbolgen option can help see what is happening in the log:
1 options symbolgen;
2 %let test=one;
3 %let one=two;
4 %let two=three;
5 %let three=last;
6
7 %put what displays is &&&&&test;
SYMBOLGEN: && resolves to &.
SYMBOLGEN: && resolves to &.
SYMBOLGEN: Macro variable TEST resolves to one
SYMBOLGEN: && resolves to &.
SYMBOLGEN: Macro variable ONE resolves to two
what displays is two
Going left to right and using brackets to show the tokens:
&&&&&test
(&&)(&&)(&test)
(&) (&) (one)
&&one
(&&)(one)
(&)(one)
&one
two

Related

Searching and importing all .csv files in a target directory

I have to scan a target directory, and import every CSV file there into SAS. I am to use separated macros to fix names of those files (they are intentionaly problematic for SAS) and actually import them. I have XCMD disabled, so pipe solution isn't usable for me (the only solution my course provided me, so several exercises are a bit problematic for me). My attempt so far is this:
%let path=my_dir
%macro fixname(badname);
%if %datatyp(%qsubstr(&badname,1,1))=NUMERIC
%then %let badname=_&badname;
%let badname=
%sysfunc(compress(
%sysfunc(translate(&badname,_,%str( ))),,kn));
%substr(&badname,1,%sysfunc(min(%length(&badname),32)))
%mend fixname;
%macro importcsv(file);
options nonotes nosource;
proc import
datafile="%superq(file)"
out=%fixname(%scan(&file,-2,.\)) replace
dbms=csv;
run;
options notes source;
%mend importcsv;
filename folder "&path";
data FilesInFolder;
length Line 8 File $300;
List = dopen('folder');
do Line = 1 to dnum(List);
File = trim(dread(List,Line));
output;
end;
drop list line;
run;
proc sql noprint;
select * into :file1-
from FilesInFolder
where File like '%.csv'
;
quit;
%macro loop;
%do i=1 %to &sqlobs;
call execute(cats('%importcsv('&path.\&&file&i')'));
%end;
%mend loop;
%loop;
Looking at logs there seems to be something wrong with call execute line, but for the life of me I cannot fix it.
As regards the requested call execute line, this will not work.
call execute(cats('%importcsv('&path.\&&file&i')'));
The cats is not properly constructed. It's unclear why you're even using cats, as you aren't really concatenating anything. It's clear why you need to use it, but not why you're currently using it.
First: you have single quotes around the string, which means it won't resolve the macro variables. That is a problem for you. In particular, you want &i to resolve. You also, though, don't want %importcsv itself to resolve.
Second: you have single quotes inside single quotes, or else you have unquoted text.
The right way to do this is:
call execute(cats('%importcsv(',"&path.\&&file&i.",')'));
The goal here is to make sure %importcsv doesn't resolve - so we wrap it in single quotes - but get the macro variables to resolve - wrapped in double quotes. It would be okay for &path not to resolve (as it's a global variable), but &&file&i absolutely needs to, since it's what you are looping over.
The final ')' is unnecessary (it could be included inside the double quotes), but I like it for readability (it makes it obvious that it's the end of the macro invocation). In this case it's unimportant, but sometimes the middle bits have more parentheses in them, and it becomes unclear when you have the right number.
All this aside, you're making this much too complicated. Here:
proc sql noprint;
select * into :file1-
from FilesInFolder
where File like '%.csv'
;
quit;
Why not simply put the macro invocation itself?
proc sql noprint;
select cats('%importcsv(',"&path.\",file,')')
into :filelist separated by ' '
from FilesInFolder
where File like '%.csv'
;
quit;
&filelist
Now you have &filelist which contains all of the macro invocations, and you can just invoke them, no macro loop needed.
You can check out my presentation on Writing Code With Your Data if you want to know more about how that works.
Of course, if this is homework and you have to do the first way, it's fine, but in the real world the second way is superior unless you have > 60k characters worth of macro calls.

SAS global variable for date [duplicate]

This question already has answers here:
How to get current month name and year in SAS using macro
(2 answers)
Closed 2 years ago.
I want to create a sas macro global variable with current month in the format OCT i.e., only first 3 letters of current month are expected in the output in upper case. Please guide me.
220 %let cm3 = %sysfunc(putn("&sysdate"d,monname,3),$upcase3.);
221 %put NOTE: &=cm3;
NOTE: CM3=OCT

SAS macro write text file with char and num parameter

I am trying to write a macro that creates a text file using parameter value. I understand all SAS parameters are passed as text, so i need to convert the text to numeric. Using INPUT for this but still getting a syntax error. Appreciate the help. Thank you.
code:
%macro test(n_var);
data _null_;
file"c:/temp/test.txt" TERMSTR=crlf;
put ;
put "(numeric variable passed = "input(&n_var,8.)")";
put ;
run;
%mend;
%test(n_var=100);
Log:
SYMBOLGEN: Macro variable N_VAR resolves to 100
NOTE: Line generated by the macro variable "N_VAR".
39 100
___
22
76
MPRINT(TEST): put "(numeric variable passed = "input(100,8.)")";
MPRINT(TEST): put ;
MPRINT(TEST): run;
ERROR 22-322: Syntax error, expecting one of the following: a name, arrayname, _ALL_, _CHARACTER_, _CHAR_, _NUMERIC_.
ERROR 76-322: Syntax error, statement will be ignored.
All SAS macro symbols (aka variables) are text. SAS macro parameters are text.
Your use case probably does not need to convert text to numeric.
Consider:
%let x = 100;
data _null_;
my_num_var = &x;
run;
The resolution of the macro variable (or perhaps better understood as 'symbol') are the letters 1 0 0, but with respect to text to be interpreted as SAS code. The data step compiler infers my_num_var is numeric from the line it sees as
my_num_var = 100;
There are some use cases where you may want to test that a macro parameter can be interpreted as a numeric value. Such use cases are probably beyond your needs at this time.
The INPUT function is one of those special DATA Step functions that is not available for use in SAS macro via the %sysfunc function. When you must 'input' a value in a 'pure' manner outside a DATA step, you will want to invoke the INPUTN or INPUTC functions via %sysfunc
%let evaluatedRepresentation = %sysfunc(inputn(&x,best8.));
The numeric evaluation of the inputn is converted to text and assigned to symbol evaluatedRepresentation.
If you are not in control of the callers to you macros in which you do ampersand evaluations the safer approach is to evaluate via SUPERQ to break code injections and other anamolies
%let evaluatedRepresentation = %sysfunc(inputn(%superq(x),best8.));

Can't execute correctly basic sas program

I am a new learner of sas language. I know that this is easy for you. I just want to understand how does it work using macro variables. In fact , I just need to run a sas program correctly. I have tow marco variables
&x=12,20,40,77
&y=12,45,54,78
I just need to compute the max and the min of them.So I write this code:
%let &x=12 20 40 77;
%let &y=12 45 54 78;
%put max min (&&x);
%put max min (&&y);
It shows me this error saying:
Open code statement recursion detected
I tried to find solution but it doesn't work !how resolve it please?
To get the error you received you have probably confused SAS. The code you posted generates these errors:
WARNING: Apparent symbolic reference X not resolved.
ERROR: Expecting a variable name after %LET.
1 %let &x=12 20 40 77;
2 %let &y=12 45 54 78;
WARNING: Apparent symbolic reference Y not resolved.
ERROR: Expecting a variable name after %LET.
3 %put max min (&&x);
WARNING: Apparent symbolic reference X not resolved.
max min (&x)
4 %put max min (&&y);
WARNING: Apparent symbolic reference Y not resolved.
max min (&y)
In the first statement you are trying to create a macro variable by using the value of the macro variable X as the name of the macro variable. But X was never defined so &X just resolved to &X. Since & is not a valid character to use in the name of a macro variable you end up with an invalidly formed %let statement.
If we fix the first two statements so that they generate valid %LET statements then the code runs. It is not clear what you wanted it to do but it works as SAS intended.
5 %let x=12 20 40 77;
6 %let y=12 45 54 78;
7 %put max min (&&x);
max min (12 20 40 77)
8 %put max min (&&y);
max min (12 45 54 78)
The extra & in the %put statements just make SAS have to take two passes to resolve the macro variable references. The first pass will resolve && to & and trigger the macro processor to make another pass. Then the second pass will properly resolve the resulting &x to the string 12 20 40 77.
It is best to work with numbers using SAS code and not macro code. If you want to find the max of a list of numbers using the MAX() function. Normally you can use the OF keyword in data steps when you have values separated by spaces but that only works with variable names, not literal values. So change the spaces to commas.
9 %put &=x ;
X=12 20 40 77
10 data _null_;
11 max=max(%sysfunc(translate(&x,%str(,),%str( ))));
12 put max=;
13 run;
max=77
If you really want to stay in macro language only then use %SYSFUNC() function again to call the MAX() function.
14 %put max=%sysfunc(max(%sysfunc(translate(&x,%str(,),%str( )))));
max=77
Refer to this link
I tried it around like this.
%let val1=17, 24, 35, 76;
%let val2=87, 32, 45, 6;
data _null_;
max=%sysfunc(max(&val1, &val2));
min=%sysfunc(min(&val1, &val2));
put max= min=;
run;
I hope it helps.

SAS macro variable issue- 'File Name value exceeds maximum length'

I'm trying to leverage macro variables (assigned via %LET) for PROC IMPORTS but it's causing the file name to exceed 201 characters.
(ERROR: File Name value exceeds maximum length of 201 characters)
Is there a different way to define variables so that they are passed as values instead of functions?
My old datafile string ends
DATAFILE="...Files_Submitted\201612\Reconcile\Q42016 Principal balances.xls"
I have changed it to
DATAFILE="...Files_Submitted\&yrmm.\Reconcile\&quarter. Principal balances.xls"
Using the following variables.
%LET EOLM= INTNX('MONTH',today(),-&MonthsAgo.);
%LET yrmm= COMPRESS(year(&EOLM.)||PUT(month(&EOLM.),z2.));
%LET qtr = COMPRESS(year(&EOLM.)||COMPRESS('Q'||CEIL(month(&EOLM.)/3)));
Thanks in advance for any/all assistance.
You're not actually passing things the right way here - you're mixing two different things, which is causing your issue.
You can either resolve the results of these functions in your macro variables, OR you can store the function calls and treat this as if you'd typed the functions into the data step - so use CATS or something to combine them.
As it is, you're ending up with a filename like "\\path\to\COMPRESS(INTNX(MONTH...", not with the results of those functions, hence your problem.
So, one option:
DATAFILE= cats("...Files_Submitted\",&yrmm.,"\Reconcile\",&quarter.,"Principal balances.xls";
That would let the functions provide their values as you expect.
The other option is to use %SYSFUNC to ask the values to be resolved in the macro variables. This is the more common way, though certainly neither is really specifically better for all purposes.
%LET EOLM= %sysfunc(INTNX(MONTH,%sysfunc(today()),-&MonthsAgo.));
And similar for the other two. Note that I remove the quotes around MONTH as quotes are not used in %SYSFUNC calls (unless you want to use quote characters themselves, but not as string delimiters).
%LET yrmm= %sysfunc(year(&EOLM.))%sysfunc(month(&EOLM.),z2.);
Note here I put the format in the SYSFUNC call directly; also note that we do not use concatenation characters in macro variables (they just produce text) and typically you don't need to use COMPRESS (though not always).
%LET qtr = %sysfunc(year(&EOLM.))Q%sysfunc(CEIL(%sysevalf(%sysfunc(month(&EOLM.))/3)));
Here we use %SYSEVALF to do the math (normally you can't have noninteger math in macro syntax). We also remove quotes from Q and just place it in line.
Putting it all together:
%let monthsAgo = 3;
%LET EOLM= %sysfunc(INTNX(MONTH,%sysfunc(today()),-&MonthsAgo.));
%put &=EOLM;
EOLM=20789
%LET yrmm= %sysfunc(year(&EOLM.))%sysfunc(month(&EOLM.),z2.);
%put &=yrmm;
YRMM=201612
%LET qtr = %sysfunc(year(&EOLM.))Q%sysfunc(CEIL(%sysevalf(%sysfunc(month(&EOLM.))/3)));
%put &=qtr;
QTR=2016Q4
Of course more easy might have been to use formats for yrmm/qtr...
%let yrmm = %sysfunc(putn(&eolm.,yymmn6.));
%let qtr = %sysfunc(putn(&eolm.,yyq6.));
%put &=yrmm &=qtr;
Or even (and this might be getting a bit cute) removing the %SYSFUNC from &EOLM and letting the %SYSFUNC format option handle the formatting. Note here EOLM does not store a number, but stores the text you see on the screen, and the number doesn't get resolved until YRMM or QTR is defined.
%LET EOLM= INTNX(MONTH,%sysfunc(today()),-&MonthsAgo.);
%let yrmm = %sysfunc(&eolm.,yymmn6.);
%let qtr = %sysfunc(&eolm.,yyq6.);
%put &=yrmm &=qtr;