Improvement on %NRQUOTE 'removal' - sas

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)

Related

remove text within parentheses of macro variable using regex

I'm unable to remove the parentheses and text within parentheses using %sysfunc(prxchange(...)). See example
%macro test(col=);
%local result;
%let result = %sysfunc(prxchange(s|\([^\)]+\)||i, -1, &col.));
%put &result.;
%mend test;
%let string = try (to) remove (this);
%test(col=%str(&string.))
ERROR: Expected close parenthesis after macro function invocation not found.
The expected output should be try remove (ignoring double spaces)
EDIT - thanks to #user667489, the simplest fix for this is
%macro test(col=);
%local result;
%let result = %sysfunc(compbl(%sysfunc(prxchange(s|%quote(\%([^\%)]+\%)||i), -1, &col.))));
%put &result.;
%mend test;
%let string = try (to) remove (this);
%test(col=%str(&string.));
I found a way of getting this to work more or less as-is:
%macro test(col=);
%local result regex;
%let regex = %sysfunc(prxparse(%str(s/\%([^\%)]+\%)//)));
%let result = %sysfunc(prxchange(&regex, -1, &col.));
%syscall prxfree(regex); /*Prevent memory leak*/
%put &result.;
%mend test;
%let string = try (to) remove (this);
%test(col=%str(&string.));
Masking the brackets within the regex with % symbols to prevent them from being parsed as SAS code and using a separate prxparse seems to do the trick.
Not sure if you can fix this because of the nature of how %sysfunc() has to convert the macro code into values to push in the function you are trying to call.
Why not just leave the PRXCHANGE() function call in actual SAS code instead?
For example you could have your macro generate a DATA step. I would recommend just passing in the NAME of the macro variable that has the value text and the NAME of the macro variable you want to assign the result into.
%macro test(invar,outvar);
%if not %symexist(&outvar) %then %global &outvar;
data _null_;
call symputx("&outvar",prxchange('s|\([^\)]+\)||i', -1,symget("&invar")));
run;
%mend test;
%let string = try (to) remove (this);
%test(invar=string,outvar=result);
%Put &=result;
Also try this:
%macro string(string);
%local new_string;
%let new_string=%sysfunc(prxchange(s/\s?\(\w+\)//,-1,&string));
%put &new_string;
%mend;
%string(%str(try (to) remove (this)));

SAS:how to use index to pick out macro array variable

I create a marco array using:
proc sql;
select distinct variable into:numVarList separated by ' ' from Map_num;
I used:
%put &numVarList{1};
and it gave me all variables:var1 var2 var3{1}
how to use index to pick out macro array variable?
update 20180305
it is strange that
%put &numVarList.;
then I got:age agenc_non_ccbt_fnd_bal chmtpd_tmpnt_bal crnyr_cnter_tdnum
%put %sysnc(scan(&numVarList.,1,str( )));
I got:age agnc_non_ccb
why?and how to fix it?
You do not create an array with your select. The result is just a string: var1 var2 var3
However you can access each element with the scan-function:
%let first_ele = %scan(&numVarList.,1,%str( ));
The result is: var1
You can also loop your string like this:
%do i=1 %to %sysfunc(countw(&numVarList.,%str( )));
%put %scan(&numVarList.,&i.,%str( ));
%end;
Concatenation of values
proc sql;
select distinct variable into:numVarList separated by ' ' from Map_num;
populates a single macro variable with a value, that can be construed as a list, which is a concatenation of the distinct values in the column named "variable".
For such a list you would scan out the individual items as shown by #zuluk.
In your case when the original values are names of variables, the resolution of the concatenation can be used directly as part of a SAS statement that accepts variable name lists, such as Proc PRINT; VAR &numVarList or DATA _NULL_; ARRAY v &numVarList
Macro array
The concept macro-array is simply a set of macro variables (which can be thought of as 'symbols' when too many 'variable' ideas are colliding) with a common basename and increasing numeric suffix. Such a set of macro variables is created by using a slightly different syntax in Proc SQL.
select distinct variable
into :symbol1-:symbol9999
from Map_num
The 9999 represents a large number that you do not expect to exceed. If the data has N <= 9999 rows then only N macro variable will be created. If N > 9999 rows only 9999 macro variables will be created. Caution: Too many macro variables can fill the macro symbol table and cause errors in your SAS. For me, Macro arrays are more a programming concept than a programming construct.
For example
Proc SQL noprint;
select name into :name1-:name9999 from sashelp.class;
%let name_count = &sqlobs;
quit;
%put NOTE: &=name1;
%put NOTE: &=name2;
%put NOTE: name&name_count=%superq(name&name_count); * almost same as next;
%put NOTE: name&name_count=&&name&name_count; * almost same as prev;
When dealing with the 'name' of the macro array in 1-level abstraction way, complete resolution is achieved by coding the 'tricky triple-hat' &&&
%macro log_macroArray (basename);
%local i count_symbol value_symbol;
%let count_symbol = &basename._count;
%do i = 1 %to &&&count_symbol;
%let value_symbol = &basename.&i;
%put NOTE: &value_symbol=&&&value_symbol;
%end;
%mend;
%log_macroArray(name);
The SAS macro system 'loops' internally during its value resolution phase and collapses the presence to && to & at each step of it's internal evaluation.
Building on #zuluk's answer, you cannot use an operator (like { }) to access a macro "array" since it's not a part of the language and it's not possible to overload operators in SAS... mostly ... but you can do a function-style macro easily.
proc sql;
select name into :namelist separated by ' '
from sashelp.class;
quit;
%macro marray(list, n);
%scan(&list.,&n.)
%mend marray;
%put %marray(&namelist,2);
That is pretty close to what you're looking for, just not quite the same syntax. If you then wanted to build new variables/etc., you could do so through the macro as well, though it might be more complicated to write a general macro given there are lots of ways you might want to do that. Here's a non-function-style version.
%macro m_to_array(list, n);
*optionally - if you want to not specify n;
%let n = %sysfunc(countw(&&&list));
%do _i = 1 %to &n;
%global &list.&_i.;
%let &list.&_i. = %scan(&&&list.,&_i.);
%end;
%mend m_to_array;
%m_to_array(namelist);
%put _global_;

passing date variable to macro for sysfunc processing

below is my little problem to create a macro and passing in a date variable. Without using the date variable, it works with results as below.
%macro x();
%let i=-1;
%let dts = %sysfunc(today());
%put &dts; /*ok*/
%let yymm1 = %sysfunc(intnx(MONTH,&dts,&i));
%put &yymm1; /*ok*/
%let mth_beg = %sysfunc(intnx(MONTH,&dts,&i,B),date9.);
%let mth_end = %sysfunc(intnx(MONTH,&dts,&i,E),date9.);
%put &mth_beg &mth_end; /*01JAN2018 31JAN2018*/
/*** proc sql code below ** */
%mend;
%x();
log:
21231
21185
01JAN2018
31JAN2018
Now I create a macro around it and got the following error:
%macro x(dts1);
%let i=-1;
/*%let dts = %sysfunc(today());*/
%let dts = %sysfunc(&dts1);
%put &dts; /*ok*/
%let yymm1 = %sysfunc(intnx(MONTH,&dts,&i));
%put &yymm1; /*ok*/
%let mth_beg = %sysfunc(intnx(MONTH,&dts,&i,B),date9.);
%let mth_end = %sysfunc(intnx(MONTH,&dts,&i,E),date9.);
%put &mth_beg &mth_end; /*01JAN2018 31JAN2018*/
/*** proc sql code below ** */
%mend;
%x(16JAN2018);
ERROR: Function name missing in %SYSFUNC or %QSYSFUNC macro function reference.
JAN2018)
ERROR: Expected close parenthesis after macro function invocation not found.
))
ERROR: Expected close parenthesis after macro function invocation not found.
ERROR: Expected close parenthesis after macro function invocation not found.
,B),date9.) ,E),date9.)
I am not sure how to let SAS treat the date passed in as a recognized date. I know i probably used the sysfunc(&dts) wrongly or the date passed in need to adhere to certain format. i just want the date to replace today(). Can you help? I am a SAS newbie.
thanks
Wrap the date in " and end with a d. That will tell SAS to convert the string to a date:
%macro x(dts1);
%let i=-1;
/*%let dts = %sysfunc(today());*/
%let dts = "&dts1"d; /*Change here!*/
%put &dts; /*ok*/
%let yymm1 = %sysfunc(intnx(MONTH,&dts,&i));
%put &yymm1; /*ok*/
%let mth_beg = %sysfunc(intnx(MONTH,&dts,&i,B),date9.);
%let mth_end = %sysfunc(intnx(MONTH,&dts,&i,E),date9.);
%put &mth_beg &mth_end; /*01JAN2018 31JAN2018*/
/*** proc sql code below ** */
%mend;
%x(16JAN2018);
change %let dts = %sysfunc(&dts1); to
%let dts = %sysfunc(inputn(&dts1,date9. ));
SAS stores dates as the number of days since 01JAN1960. So if you do not attach a date format to the date value it will just look like an integer.
%let today=%sysfunc(today());
You can then use that integer anywhere you would use a date value.
%let next_month=%sysfunc(intnx(month,&today,1,b));
You can also represent dates by using a date literal. To make a date literal you represent the date value using something the DATE informat can read (like 16FEB2018, 16feb18, 16-FEB-2018, etc.) enclosed in quotes with the letter d appended.
%let today="%sysfunc(today(),date9)"d ;
%let date_string=13FEB2018;
%let date_value="&date_string"d ;
So date literals will work in SAS code and when you use the %sysfunc() macro function to call a SAS function (like INTNX) and they will work in the %sysevalf() macro function. But the %eval() macro function will not recognize date literals. So you will need to use %sysevalf() if you want use arithmetic or comparisons of date literals in macro logic.
%if %sysevalf(&today > '01JAN2018'd) %then ....
%let tomorrow=%sysevalf(&today +1);

A character operand was found in the %EVAL function or %IF condition

/* Here is the code that is having an issue*/
%macro numstats(var = ,file=, format=);
%let dsid=open(&file.,i);
%if %LENGTH(&var.) > 8 AND %VARTYPE(&dsid.,%VARNUM(&dsid.,&var.))='N' %then %do;
Proc SQL;
SQL code
quit;
end;
%mend numstats;
I am having the following error when running this code: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required.
I have played with this code extensively to try different online solutions and cannot figure out why I am still receiving this issue. Please help!
Side Note: The reason I had to add this IF statement is to try to find out whether the variable was a date variable. All my date variables are Date9. so if it is Numeric and has a length greater than 8, then I want to add date formatting that I would not add to regular numeric variables. If anyone can think of an easier way to do that, then i am open to that as well, but please help me figure out this error!
If you want to call data step functions within macro code then you need to call them with the %SYSFUNC() macro function. Here is an example of a macro function that will do that for you.
%macro varinfo
/*----------------------------------------------------------------------
Retrieve attribute of a specified variable.
----------------------------------------------------------------------*/
(ds /* Data set name */
,var /* Variable name */
,info /* information attribute to return - Default is NUM */
);
/*----------------------------------------------------------------------
Example values for INFO parameter:
NUM = variable number
LEN = length of variable
FMT = format of variable
INFMT = informat of variable
LABEL = label of variable
TYPE = type of variable (N for numeric, C for character)
------------------------------------------------------------------------
Usage Examples:
%if %varinfo(&data,NAME)
%then %put input data set contains variable NAME;
%put Variable &column in &data has type %varinfo(&data,&column,type);
------------------------------------------------------------------------
Notes:
The macro call resolves to 0 when either the data set does not exist
or the variable is not in the specified data set.
Invalid values for the INFO parameter generate a SAS ERROR message.
----------------------------------------------------------------------*/
%local dsid rc varnum;
%let dsid = %sysfunc(open(&ds));
%if (&dsid) %then %do;
%let varnum = %sysfunc(varnum(&dsid,&var));
%if (&varnum) & %length(&info) %then
%sysfunc(var&info(&dsid,&varnum))
;
%else
&varnum
;
%let rc = %sysfunc(close(&dsid));
%end;
%else 0;
%mend varinfo;
Using this your macro might become something like this:
%macro numstats(var = ,file=, format=);
%if %varinfo(&file,&var,type)=N and
DATE = %sysfunc(substrn(%varinfo(&file,&var,fmt),1,4))
%then %do;
* do something ;
%end;
%mend numstats;

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);