Strange behaviour with %sysfunc, cat and '<>' (SAS Macro) - sas

I am trying to concatenate with '<' and '>'. However, I am getting 0 digits as output instead:
74 %let mname = ABC;
75 %put &mname;
ABC
76 %let mname2 = %sysfunc(cat(&mname., <, 2, >));
77 %put &mname2;
ABC020
How do I resolve this?

You don't need to use %sysfunc(cat(...)) in macro. Use a literal macro concatenation instead
%let mname3 = &mname.<2>;
%put NOTE: &=mname3;
It appears the root cause of your observation is that %sysfunc is performing an implicit evaluation (either %eval or %sysevalf) of the resolved macro arguments before invoking cat. The bare < is evaluated to 0 (%put %eval(<); confirms this). A bare = would get evaluated to 1

Related

Inconsistent Macro Variable Data Type

I'm trying to make a simple macro that checks whether a specific macro variable is either missing or does not exist. Normally, this would require two statements: a %symexist, and if it does exist, additional logic to detect whether it's a null value. The below code combines all of that into one.
%macro isnull(macvar);
%sysevalf(%superq(%superq(macvar)) NE %str(), boolean);
%mend isnull;
Problem
I cannot use %isNull() in a %if statement, because the returned value always seems to be a character. This behavior differs if it's in open code or within a macro itself.
What I've tried
I've narrowed it down to the macro not resolving as a numeric value. I've tried everything from enclosing it with %sysfunc(putn()) to %cmpres() to %sysfunc(compress()). If it's in open code, it is numeric. If it's in another macro, it's character. You can see it with this code:
/* Miss2 resolves incorrectly as character */
%macro check;
%let miss1=%sysevalf(%superq(asdf) =, boolean);
%let miss2=%isNull(asdf);
%put Miss1: %datatyp(&miss1);
%put Miss2: %datatyp(&miss2);
%mend;
%check;
/* Miss2 resolves correctly as numeric */
%let miss1=%sysevalf(%superq(asdf) =, boolean);
%let miss2=%isNull(asdf);
%put Miss1: %datatyp(&miss1);
%put Miss2: %datatyp(&miss2);
Want
I want to be able to use this in a %if statement to check whether a macro both exists and is not blank simultaneously.
%macro foo;
%if(%isNull(sysuserid) = 1) %then %put sysuserid exists;
%if(%isNull(asdffdsa) = 0) %then %put asdffdsa does not exist;
%if(%isNull(sysuserid) > 0) %then %put this should resolve;
%if(%isNull(asdffdsa) > 0) %then %put this should not resolve;
%mend;
%foo;
The problem you have here is that your macro has a semicolon in it. See this:
174 %macro check;
175 %let miss1=%sysevalf(%superq(asdf) NE %str(), boolean);
176 %let miss2=%missm(asdf);
177
178 %put &miss1. Miss1: %datatyp(&miss1);
179 %put &miss2. Miss2: %datatyp(%unquote(&miss2));
180 %mend;
181 %check;
WARNING: Apparent symbolic reference ASDF not resolved.
WARNING: Apparent symbolic reference ASDF not resolved.
0 Miss1: NUMERIC
0; Miss2: CHAR
Note the ;? Compile this instead:
%macro missm(macvar);
%sysevalf(%superq(%superq(macvar)) NE %str(), boolean)
%mend missm;
and you get:
185 %macro check;
186 %let miss1=%sysevalf(%superq(asdf) NE %str(), boolean);
187 %let miss2=%missm(asdf);
188
189 %put &miss1. Miss1: %datatyp(&miss1);
190 %put &miss2. Miss2: %datatyp(%unquote(&miss2));
191 %mend;
192 %check;
WARNING: Apparent symbolic reference ASDF not resolved.
WARNING: Apparent symbolic reference ASDF not resolved.
0 Miss1: NUMERIC
0 Miss2: NUMERIC
I'll also add that I think you should not skip the %symexist. You get a warning in the log the way you're doing it here, that's easily avoided.
%macro missm(macvar);
%if %symexist(&macvar.) %then
%sysevalf(%superq(%superq(macvar)) NE , boolean)
%else
0
%mend missm;
You'll also note I remove your unnecessary %str() which doesn't really do anything. See Chang Chung's seminal paper, Is This Macro Parameter Blank, for why (and for some more great information, if you haven't already read it).
Finally - I think I would suggest renaming your macro and/or reversing the direction. %if %missm says to me 'if this macro variable is missing', which is the opposite of what you're telling: TRUE is returned if it is NOT missing. %missm should return true for EQ [blank], or NOT %symexist; it should return false for [defined and contains a value].

Extracting Digits from Year and Concatenation

I have variable x = 2001. I want to produce a string that looks like this:
"TEST0106.xls". The 01 in that string is the last two digits of 2001, and the 06 is the last two digits of 2006 (x+5).
My current code is this:
%let x = 2001;
%let sub1 = %sysfunc(mod(&x, 100));
%let sub2 = %sysfunc(mod(&x+5, 100));
%let test = TEST&sub1&sub2.xls;
%put &test;
However, this just gives me "TEST16xls" since the 0 disappears in the modulus division and I'm not sure why the period isn't there. I believe I'll have to do some approach where I convert the numbers into characters and do a sub-string. How would I go about this task?
First, you don't need to use modulo arithmetic to do a substring in macro variable land, everything is text, so just use %substr.
Second, you can give an optional argument to %sysfunc telling it how to format the result; use the z2 format to tell it what you want. I think leaving the modulo is convenient here simply because it does give you that option. Otherwise you can use %sysfunc(putn( if you want to not use modulo.
Third, you need another . since the first one ends the macro variable (technically macro variables are & to . inclusive unless they hit another character that is invalid for macro variable names first).
%let x = 2001;
%let sub1 = %substr(&x,3,2);
%let sub2 = %sysfunc(mod(&x+5, 100),z2.);
%let test = TEST&sub1.&sub2..xls;
%put &test;
or
%let sub2 = %sysfunc(putn(&sub1.+5,z2.));

Improvement on %NRQUOTE 'removal'

I have the following piece of code that works, but I'd like to know if anyone can come up with a better way of 'removing' the %nrquote. I have had to add a %SUBSTR function, which works, but I'm keen to know if there are any other suggestions, and if anyone can help explain why the code doesn't work without the %let statement within the mvar macro definition.
/* Automatically generated by DI Studio - cannot change */
%let _where_clause = %nrquote(name = %'Henry%');
%let _mac1 = %nrquote(lemk);
%let _variable = weight;
%let _input0 = sashelp.class;
/* End of auto-generated code */
options mprint;
%macro mvar;
%if &_where_clause ^= %then %do;
/* Re-assign the _where_clause variable to 'remove' %nrquote */
%let _where_clause = %substr(&_where_clause,1);
where &_where_clause
%end;
%mend mvar;
proc sql;
select &_variable into :&_mac1
from &_input0
%mvar
;
quit;
Without the %let statement, the code fails with this error:
NOTE: Line generated by the macro variable "_WHERE_CLAUSE".
1 name = 'Henry'
-
22
MPRINT(MVAR): where name = '
NOTE: Line generated by the macro variable "_WHERE_CLAUSE".
1 name = 'Henry'
-
200
ERROR 22-322: Syntax error, expecting one of the following: a name, a quoted string,
a numeric constant, a datetime constant, a missing value, (, *, +, -, ALL, ANY,
BTRIM, CALCULATED, CASE, INPUT, PUT, SELECT, SOME, SUBSTRING, TRANSLATE, USER.
ERROR 200-322: The symbol is not recognized and will be ignored.
114 ;
MPRINT(MVAR): Henry'
You need %UNQUOTE which is what is happening with %LET, it is un-quoting the quoted quotes.
Change
where &_where_clause
to
where %unquote(&_where_clause)

SAS store macro reference in macro variable

I want to store a list of macro references in another macro variable and then change the content of one of the referenced variables.
As example:
%LET String=FirstString;
%LET KeepMacroNotString=&String;
%PUT &String = &KeepMacroNotString ?;
%LET String=String changed;
%PUT &String = &KeepMacroNotString?;
In the end I would like that %PUT &KeepMacroNotString resolves to "String changed". However it sticks to the first assignment.
Any ideas?
Thx, Lubenja
Much easier to do with a data step.
data _null_;
call symputx('KeepMacroNotString','&String');
run;
I found the solution:
A combination of the %NRSTR function and the %UNQUOTE function do the trick:
%LET String=FirstString;
%LET KeepMacroNotString=%NRSTR(&String);
%PUT &String = &KeepMacroNotString ?;
%LET String=String changed;
%PUT &String = %UNQUOTE(&KeepMacroNotString)?;
Explanation: First you have to mask the "&" to prevent the macro from being resolved (%NRSTR()).
But when you want to use the marco, then you have to unquote it again (%UNQUOTE()).
I wouldn't use this approach, but if you don't mind warning messages in the log (I do), you could in theory just change the order of your statements:
WARNING: Apparent symbolic reference STRING not resolved.
57
58 %LET KeepMacroNotString=&String;
59
60 %LET String=FirstString;
61 %PUT &String = &KeepMacroNotString ?;
FirstString = FirstString ?
62
63 %LET String=String changed;
64 %PUT &String = &KeepMacroNotString?;
String changed = String changed?
This is basically an ugly way of accomplishing the same sort of indirection that Tom did more gracefully. Key point being that that the macro variable KeepMacroNotString is given a value of &String, not the resolved value.
In a macro setting, this can also be accomplished by assigning default value as a macro variable reference, e.g.:
59 %macro indirect(String=
60 ,KeepMacroNotString=&string
61 );
62 %put _local_;
63 %put &string = &keepmacronotstring;
64 %mend indirect;
65 %indirect(string=Hi)
INDIRECT KEEPMACRONOTSTRING &string
INDIRECT STRING Hi
Hi = Hi

How do I work out the data type of my macro variable in SAS

How do I print out the data type of a macro variable in the log
%macro mymacro(dt2);
%LET c_mth = %SYSFUNC(intnx(month,&dt2.d,-1,e),date9.) ;
%put &c_mth;
%mend;
mymacro('01sep2014')
I have a bunch of macro variables assigned using a %let or into:
my problem is I'm trying to do a bunch of boolean condition on dates but I suspect that some of my variables are strings and some are dates
I have casted them in my code but to triple check there is surely a way to return something to the log
I want something similar to using str() or mode() or is.numeric() in R
H,
The SAS macro language is weird. : )
As Reeza said, macro variables do not have a type, they are all text.
But, if you use Boolean logic (%IF statement), and both operands are integers, the macro language will do a numeric comparison rather than a character comparison.
So you can use the INPUTN() function to convert the date strings to SAS dates (number of days since 01Jan1960), and then compare those. Here's an example, jumping off from your code:
%macro mymacro(dt1,dt2);
%local c_mth1 c_mth2 n_mth1 n_mth2;
%let c_mth1 = %sysfunc(intnx(month,&dt1.d,-1,e),date9.) ;
%let c_mth2 = %sysfunc(intnx(month,&dt2.d,-1,e),date9.) ;
%let n_mth1 = %sysfunc(inputn(&c_mth1,date9.)) ;
%let n_mth2 = %sysfunc(inputn(&c_mth2,date9.)) ;
%put &c_mth1 -- &n_mth1;
%put &c_mth2 -- &n_mth2;
%if &n_mth1<&n_mth2 %then %put &c_mth1 is before &c_mth2;
%else %put &c_mth1 is NOT before &c_mth2;
%mend;
Log from a sample call:
236 %mymacro('01feb1960','01mar1960')
31JAN1960 -- 30
29FEB1960 -- 59
31JAN1960 is before 29FEB1960
--Q.
Macro variables do not have a type, they are all text.
You have to make sure the variable is passed in a way that makes sense to the program and generates valid SAS code.
%let date1=01Jan2014;
%let date2=31Jan2014;
data _null_;
x = "&date1"d > "&date2"d;
y = "&date2"d > "&date1"d;
z = "&date2"d-"&date1"d;
put 'x=' x;
put 'y=' y;
put 'z=' z;
run;
Log should show:
x=0
y=1
z=30
If your macro variables resolve to date literals, you can use intck combined with %eval to compare them, e.g.
%let mvar1 = '01jan2015'd;
%let mvar2 = '01feb2015'd;
/*Prints 1 if mvar2 > mvar1*/
%put %eval(%sysfunc(intck(day,&mvar1,&mvar2)) > 0);