how to use column value in SAS macro do loop - sas

I have a problem with SAS macro. I used do% %then loop command in proc means, but function %index uses the column name as a simple string, not as a column name. But need to check the values in the column.
Here's the macro I wrote.
%let varlist = mzda mzdy vyplat odmena wage payroll SALARY INSTRUCT PAYMENT;
%macro quickndirty;
%let it_numb = %sysfunc(countw(&varlist.));
proc means data=CZ_DATA.TRNS_FNCP_D (keep=DB_CLIENT_ID DB_ACCOUNT PAYMENT_INSTRUCT DT_REP DB_BANK_BIC DB_CLIENT_ID)noprint nway;
class DB_CLIENT_ID DB_ACCOUNT PAYMENT_INSTRUCT;
output out= SALARY_P2 (keep = DB_CLIENT_ID DB_ACCOUNT PAYMENT_INSTRUCT _FREQ_ );
where DT_REP = &report_date.
%do i=1 %to &it_numb.;
%if %index(PAYMENT_INSTRUCT, %scan(&varlist., &i.))>0 %then
%do;
and 1=1
%end;
%else %if &i. = &it_numb. %then
%do;
and 1=0
%put zase 2;
%end;
%end;
;
run;
%mend;
%quickndirty
data sample:
DB_CLIENT_ID DB_ACCOUNT PAYMENT_INSTRUCT DT_REP
79325 1387348961 SALARY 01/20 11feb2020
79322 1387355558 SALARY 02/20 11feb2020
will anyone advise me how to modify the code to use the values from the column? The code is not complete because I must fix this problem first.
I used the loop instead of the following command. if someone has better solution than to loop statement, can you give me some advice? But it must be still macro. But it must still be a macro, the same code will run several times and I don't want to copy the same code again and again.
and (PAYMENT_INSTRUCT contains 'mzda' or
PAYMENT_INSTRUCT contains 'mzdy' or
PAYMENT_INSTRUCT contains 'vyplat' or
PAYMENT_INSTRUCT contains 'odmena' or
PAYMENT_INSTRUCT contains 'wage' or
PAYMENT_INSTRUCT contains 'payroll' or
PAYMENT_INSTRUCT contains 'salary')
Thanks

Looks like for an input of
%let varlist = mzda mzdy vyplat ;
you want to generate this code:
PAYMENT_INSTRUCT contains "mzda"
or PAYMENT_INSTRUCT contains "mzdy"
or PAYMENT_INSTRUCT contains "vyplat"
So your macro logic should be something like:
%let sep=;
%do i=1 %to %sysfunc(countw(&varlist));
&sep PAYMENT_INSTRUCT contains "%scan(&varlist,&i)"
%let sep=or;
%end;

Related

Check whether item is in SAS list

I have a SAS list. This SAS list is stored in a macro variable. Please assume that I have no table to derive this SAS list.
The SAS list contains names separated by commas. An example of the SAS list macro variable:
%LET sas_list = name1,name2,name3;
I want to check whether macro-variable “item” is present in the list.
Something like:
%IF &item. IN &sas_list. %THEN %DO;
Whatever;
%END;
For some reason, I get the error:
“A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was: name1 IN name1,name2,name3”
Help is appreciated.
You need to use two options here:
MINOPERATOR, this will allow the use of the IN operator
MINDELIMITER, this will allow you to set the delimiter
%let sas_list = name1,name2,name3;
options minoperator mindelimiter=',';
%Macro want(item);
%if &item. in &sas_list. %then %put i = 1;
%else %put i = 0;
%mend;
%want(name1);
i = 1
Add the minoperator and mindelimiter system options. These stand for Macro IN Operator and Macro IN Delimiter.
options minoperator mindelimiter=',';
%LET sas_list = name1,name2,name3;
%LET item = name1;
%IF &item. IN &sas_list. %THEN %DO;
%put &item is in &sas_list;
%END;
Output:
name1 is in name1,name2,name3
If you want to find values that are not in a list, pass it through %eval.
options minoperator mindelimiter=',';
%LET sas_list = name1 name2 name3;
%LET item = name4;
%IF %eval(&item. IN &sas_list.) = 0 %THEN %DO;
%put &item NOT in &sas_list;
%END;
Output:
name4 is NOT in name1,name2,name3
Note that you can also supply these options directly in a macro if you only want in to work selectively. For example:
%macro foo / minoperator mindelimiter=',';
...
%mend;
You can also use the FINDW funtion to ascertain the presence of an item in a list.
%if %sysfunc(FINDW(%upcase(%superq(saslist)), %upcase(&item), %str(,))) %then %do;
...
%end;
Supporting
%LET sas_list = name1,name2,name3;
you can find if name2 is in the list this way.
data test;
it = findw("&sas_list", 'name2',',');
run;
but I assume you want to find it out without using a data step, so
%let het = %sysfunc(findw(%quote(&sas_list),name2,%quote(,)));
%put NOTE: het is &het;
does the job. Note
you find is not a macro function, so you need %sysfunc() to call it in a macro statement
you have to mask the comma's with %quote(). Otherwise they would be considered argument separators.

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;

Creating datasets within Macro SAS

I have the following code:
%macro MSA (Data=, Code=, MSAName=);
data &Data;
set NoDup;
%if MSA = &Code %then %do;
MSA_name = "&MSAName";
output &data;
%end;
run;
%mend MSA;
%MSA (Data=Bakersfield, Code=12540, MSAName=Bakersfield);
%MSA (Data=Chico, Code=17020, MSAName=Chico);
So I get two datasets as I want with one names Bakersfield and another Chico. However, the MSA column does not display the correct value (i.e. a column of 12540 for Bakersfield and 17020 for Chico for MSA), nor do I get a variable named MSA_Name that gives me the correct values (Bakersfield for all of the MSA_Name column, and Chico). What am I doing wrong?
The issues you have with your code is to mingle macro syntax with data step. Please try the following:
%macro MSA (Data=, Code=, MSAName=);
data &Data;
set NoDup;
if MSA = &Code /*if MSA is char, you will need quote "&code"*/ then do;
MSA_name = "&MSAName"; output; end;
run;
%mend MSA;
%MSA (Data=Bakersfield, Code=12540, MSAName=Bakersfield);
%MSA (Data=Chico, Code=17020, MSAName=Chico);

Setting Macro Variable equal to value in table

I have a table with one row and 4 columns. I would like to create 4 macro variables named after each column where the value is set to the value in the 1 row.
If this were R I could access the values directly with something like:
newvar1=tablename[1,1]
newvar2=tablename[1,2]...
Is there anyway for me to select values from a table and set macrovariables equal to that value?
So something like:
%macrovar1=tablename[1,1]...
Except obviously the right side of the equals sign is R code not SAS.
Thanks
You can use proc sql to do it like so:
proc sql noprint inobs=1;
select name into :my_val from sashelp.class;
quit;
%put &my_val;
Or you can use call symput from the datastep like this:
data _null_;
set sashelp.class(obs=1);
call symput('my_val',name);
run;
For something more flexibile, we use a utility macro that allows us to check values from anywhere in our code. I've modified it slightly to accommodate your request but the usage will be like this:
%let my_val = %get_val(iDs=sashelp.class, iField=name);
You could also use it in the middle of a proc or a datastep like so:
data _null_;
my_value = "%get_val(iDs=sashelp.class, iField=name)";
run;
Or even:
proc sql noprint;
create table want as
select * from sashelp.class
where name = "%get_val(iDs=sashelp.class, iField=name)"
;
quit;
Here is the macro definition:
%macro get_val(iDs=, iField=);
%local dsid pos rc result cnt value;
%let result=;
%let cnt=0;
/*
** ENSURE ALL THE REQUIRED PARAMETERS WERE PASSED IN.
*/
%if "&iDs" ne "" and "&iField" ne "" %then %do;
%let dsid=%sysfunc(open(&iDs,i));
%if &dsid %then %do;
%let pos=%sysfunc(varnum(&dsid,&iField));
%if &pos %then %do;
%let rc=%sysfunc(fetch(&dsid));
%if "%sysfunc(vartype(&dsid,&pos))" = "C" %then %do;
%let value = %qsysfunc(getvarc(&dsid,&pos));
%if "%trim(&value)" ne "" %then %do;
%let value = %qtrim(&value);
%end;
%end;
%else %do;
%let value = %sysfunc(getvarn(&dsid,&pos));
%end;
&value
%end;
%else %do;
%put ERROR: MACRO.GET_VAL.SAS: FIELD &iField NOT FOUND IN DATASET %upcase(&iDs).;
%end;
%end;
%else %do;
%put ERROR: MACRO.GET_VAL.SAS: DATASET %upcase(&iDs) COULD NOT BE OPENED.;
%end;
%let rc=%sysfunc(close(&dsid));
%end;
%else %do;
%put ERROR: MACRO.GET_VAL.SAS: YOU MUST SPECIFY BOTH THE IDS AND IFIELD PARAMETERS TO CALL THIS MACRO.;
%end;
%mend;
The above macro is an abbreviated version of the %ds2list macro found here.
There are several ways, but the simplest is:
data _null_;
set have;
if _n_=1 then do;
call symputx('macrovar1',var1);
*more of these;
end;
stop;
run;
That opens the dataset, then if on first row (_n_ is iteration of data step loop, which in most cases is the first row), use call symputx to assign its value to a macro variable.
I would note that you should remember that SAS macro variables are not data variables, do not have a data type (always are text), and usually are not used to store data the way you would use vectors in R.

A macro function to produce a macro variable from a data variable

data sample;
input x $;
datalines;
one
two
three
;
%macro variable_to_macvar(variable=, dataset=);
proc sql noprint;
select &variable into : outlist separated by ' '
from &dataset;
quit;
&outlist
%mend variable_to_macvar;
%put %variable_to_macvar(variable=x, dataset=sample);
Expected output: one two three. Instead I get an error. Why? Is this fixable?
I've successfully created other macros of a very similar form, where the function "returns" a value using the &macrovariable at the end of the macro without a semicolon. For example, here is a similar type of function that works:
%macro zcat(first=5, last=15, prefix=freq);
%let x=;
%do i = &first %to &last;
%let x=&x &prefix.&i;
%end;
&x
%mend zcat;
%put %zcat();
You cannot execute a macro that involves running a proc or a data step in the way that you're trying to do here. You would need to use something like %sysfunc(dosubl(proc sql...)) in order for that to work (assuming you have SAS 9.3+ - see Joe's answer above). Otherwise, you can't use proc sql within a function-style macro.
More details about dosubl:
http://support.sas.com/documentation/cdl/en/lefunctionsref/67398/HTML/default/viewer.htm#p09dcftd1xxg1kn1brnjyc0q93yk.htm
It would be a bit fiddly, but if you really wanted to make this work as a function-style macro in earlier versions of SAS, you could construct it using the open, fetchobs and getvarc functions instead.
Update: Here's an example (using call set rather than getvarc, as this turned out to be simpler), in case anyone needs to do this in SAS 9.2 or earlier.
%macro variable_to_macvar(var,ds);
%local rc dsid i;
%let &var =;
%global outlist;
%let outlist=;
%let dsid = %sysfunc(open(&ds,i));
%syscall set(dsid);
%let rc = 0;
%let i = 0;
%do %while(&rc = 0);
%let i = %eval(&i + 1);
%let rc = %sysfunc(fetchobs(&dsid,&i));
%if &rc = 0 %then %let outlist = &outlist &&&var;
%end;
%let rc = %sysfunc(close(&dsid));
&outlist
%mend;
%put %variable_to_macvar(var=x, ds=sample);
Now works for views as well as ordinary datasets.
DOSUBL is available (but experimental) in 9.3 (at least, 9.3TS1M2, which I have). This is how you'd do it.
data sample;
input x $;
datalines;
one
two
three
;
%macro variable_to_macvar(variable=, dataset=);
%let rc=%sysfunc(dosubl(%str(
proc sql noprint;
select &variable into : outlist separated by ' '
from &dataset;
quit;
)));
&outlist
%mend variable_to_macvar;
%put %variable_to_macvar(variable=x, dataset=sample);;
If you can't use DOSUBL, or want to avoid experimental things, you can do this with PROC FCMP rather than a macro. If you like to write functions, PROC FCMP is probably for you: actually being able to write functions, rather than having to deal with the annoyances of the macro language.
Alter your code at the end to
%global outlist;
%variable_to_macvar(variable=x, dataset=sample);
%put &outlist;
The %put wants to resolve only a macro variable or a single value. It cannot call a procedure. So call your macro and then print the result.
Also, delete the &outlist from the macro definition. Sorry I missed that initially.
EDIT: Alternative.
Change your macro definition to
%macro variable_to_macvar(variable=, dataset=);
proc sql noprint;
select &variable into : outlist separated by ' '
from &dataset;
quit;
%put &outlist
%mend variable_to_macvar;
Just do the %put inside the macro.
%variable_to_macvar(variable=x, dataset=sample);
will print the string to the log.
We have a utility macro that is probably one of our most used pieces of code that does this for us. It is similar to the code that #user667489 provided but includes some nice features including error catching, allows both character and numeric vars, allows you to specify seperators, quotes, quote characters, filters to the dataset, etc....
We just put this macro in our autocall library so that it's avaialble to all of our programs. Some examples of running the macro:
Example 1 - Default behaviour:
%put %variable_to_macvar(var=x, ds=samplex);
Result 1:
one,two,three
Not quite the desired output as the default seperator is a comma, this is easily changed though...
Example 2 - Specify to use a space character as a delimiter:
%put %ds2list(iDs=samplex, iField=x, iDelimiter=%str( ));
Result 2:
one two three
Example 3 - Quoting & example usage
data names;
input name $;
datalines;
John
Jim
Frankie
;
run;
%put %ds2list(iDs=names, iField=name, iQuote=1);
proc sql noprint;
create table xx as
select *
from sashelp.class
where name in (%ds2list(iDs=names, iField=name, iQuote=1))
;
quit;
Result 3:
The below is printed to the log:
'John','Jim','Frankie'
Notice how we don't need to even save the result to a macro variable to use it in the SQL statement! Swweeet! This works just as well for SQL passthrough queries, and any other data step or proc statement that you can throw it at. In the above example, a single row is returned as 'John' is the only match found...
Anyway, that's our solution here... been using this for >10 years and works well for me. Here is the macro:
/***************************************************************************
** PROGRAM: MACRO.DS2LIST.SAS
**
** UTILITY PROGRAM THAT DETECTS RETURNS A LIST OF FIELD VALUES FROM A
** DATASET IN DELIMITED FORMAT.
**
** PARAMETERS:
** iDs : THE LIBNAME.DATASET NAME THAT YOU WANT TO CHECK.
** iField : THE FIELD THAT CONTAINS THE VALUES YOU WANT RETURNED IN A
** DELIMITED FORMAT.
** iDelimiter: DEFAULT IS A COMMA. THE DELIMITER TO USE FOR THE RETURNED LIST.
** iDsOptions: ANY STANDARD DATASET OPTIONS THAT YOU WOULD LIKE TO APPLY SUCH
** AS A WHERE STATEMENT.
** iQuote : (0=NO,1=YES). DEFAULT=0/NO. DETERMINES WHETHER THE RETURNED
** LIST IS QUOTED OR NOT.
** iQuoteChar: (SINGLE,DOUBLE) DEFAULT=SINGLE. SPECIFIES WHETHER SINGLE
** OR DOUBLE QUOTES ARE USED WHEN QUOTING THE RETURNED LIST
**
*****************************************************************************
** VERSION:
** 1.8 MODIFIED: 11-OCT-2010 BY: KN
** ALLOW BLANK CHARACTER VALUES AND ALSO REMOVED TRAILING
** ALLOW PARENTHESES IN CHARACTER VALUES
*****************************************************************************/
%macro ds2list(iDs=, iField=, iDsOptions=, iDelimiter=%str(,), iQuote=0, iQuoteChar=single);
%local dsid pos rc result cnt quotechar;
%let result=;
%let cnt=0;
%if &iQuote %then %do;
%if "%upcase(&iQuoteChar)" eq "DOUBLE" %then %do;
%let quotechar = %nrstr(%");
%end;
%else %if "%upcase(&iQuoteChar)" eq "SINGLE" %then %do;
%let quotechar = %nrstr(%');
%end;
%else %do;
%let quotechar = %nrstr(%");
%put WARNING: MACRO.DS2LIST.SAS: PARAMETER IQUOTECHAR INCORRECT. DEFAULTED TO DOUBLE;
%end;
%end;
%else %do;
%let quotechar = ;
%end;
/*
** ENSURE ALL THE REQUIRED PARAMETERS WERE PASSED IN.
*/
%if "&iDs" ne "" and "&iField" ne "" %then %do;
%let dsid=%sysfunc(open(&iDs(&iDsOptions),i));
%if &dsid %then %do;
%let pos=%sysfunc(varnum(&dsid,&iField));
%if &pos %then %do;
%let rc=%sysfunc(fetch(&dsid));
%do %while (&rc eq 0);
%if "%sysfunc(vartype(&dsid,&pos))" = "C" %then %do;
%let value = %qsysfunc(getvarc(&dsid,&pos));
%if "%trim(&value)" ne "" %then %do;
%let value = %qsysfunc(cats(%nrstr(&value)));
%end;
%end;
%else %do;
%let value = %sysfunc(getvarn(&dsid,&pos));
%end;
/* WHITESPACE/CARRIAGE RETURNS REMOVED IN THE BELOW LINE */
/* TO ENSURE NO WHITESPACE IS RETURNED IN THE OUTPUT. */
%if &cnt ne 0 %then %do;%unquote(&iDelimiter)%end;%unquote(&quotechar&value&quotechar.)
%let cnt = %eval(&cnt + 1);
%let rc = %sysfunc(fetch(&dsid));
%end;
%if &rc ne -1 %then %do;
%put WARNING: MACRO.DS2LIST.SAS: %sysfunc(sysmsg());
%end;
%end;
%else %do;
%put ERROR: MACRO.DS2LIST.SAS: FIELD &iField NOT FOUND IN DATASET %upcase(&iDs).;
%end;
%end;
%else %do;
%put ERROR: MACRO.DS2LIST.SAS: DATASET %upcase(&iDs) COULD NOT BE OPENED.;
%end;
%let rc=%sysfunc(close(&dsid));
%end;
%else %do;
%put ERROR: MACRO.DS2LIST.SAS: YOU MUST SPECIFY BOTH THE IDS AND IFIELD PARAMETERS TO CALL THIS MACRO.;
%end;
%mend;