Getting error while running SAS code - sas

I'm getting an error while running the below code. &CNT is 50 and &vars has column names in it.
Each column as some values from 1 to 100. I want to select each column and check the below criteria (%if statement), creating a new variable and assigning the values to it (like free, partially free and not free).
option mlogic mprint;
%macro analysis();
DATA Q2;
SET Q1;
%do i=1 %to &CNT.;
%let segs =%scan(&VARS.,&i.," ");
%IF &SEGS.<=2.5 %THEN &SEGS._R="FREE";
%ELSE %IF (&SEGS.>2.5 AND &SEGS.<5.5) %THEN &SEGS._R="PARTLY FREE";
%ELSE %IF &SEGS.>=5.5 %THEN &SEGS._R="NOT FREE";
/*%PUT &segs.;*/
%end;
RUN;
%MEND;
%analysis();
This is the output I'm getting:
SAS LOG ERROR:
MPRINT(ANALYSIS): DATA Q2;
MPRINT(ANALYSIS): SET Q1;
MLOGIC(ANALYSIS): %DO loop beginning; index variable I; start value is 1; stop value is 56; by value
is 1.
MLOGIC(ANALYSIS): %LET (variable name is SEGS)
MLOGIC(ANALYSIS): %IF condition &SEGS.<=2.5 is FALSE
MLOGIC(ANALYSIS): %IF condition (&SEGS.>2.5 AND &SEGS.<5.5) is FALSE
MLOGIC(ANALYSIS): %IF condition &SEGS.>=5.5 is TRUE
MLOGIC(ANALYSIS): %PUT &segs.
yr1960
MLOGIC(ANALYSIS): %DO loop index variable I is now 2; loop will iterate again.
MLOGIC(ANALYSIS): %LET (variable name is SEGS)
MLOGIC(ANALYSIS): %IF condition &SEGS.<=2.5 is FALSE
MLOGIC(ANALYSIS): %IF condition (&SEGS.>2.5 AND &SEGS.<5.5) is FALSE
MLOGIC(ANALYSIS): %IF condition &SEGS.>=5.5 is TRUE
***NOTE: Line generated by the macro variable "SEGS".
1 yr1961_R
--------
22

You are confusing IF conditions inside the Macro processor versus inside the Data Step. I think this is what you want.
%macro analysis();
DATA Q2;
SET Q1;
%do i=1 %to &CNT.;
%let segs =%scan(&VARS.,&i.," ");
IF &SEGS.<=2.5 THEN &SEGS._R="FREE";
ELSE IF (&SEGS.>2.5 AND &SEGS.<5.5) THEN &SEGS._R="PARTLY FREE";
ELSE IF &SEGS.>=5.5 THEN &SEGS._R="NOT FREE";
/*%PUT &segs.;*/
%end;
RUN;
%MEND;
%analysis();
Macros write code for you. You were comparing the variable Name to the constant values (using string ordering, no less), not the variable values versus the constant values using numbers.

Related

Why is the %IF statement not executing?

%macro SASIsAwful();
%let LastEnt = 12;
%let i = 1;
%do i = 1 %to &LastEnt.;
proc sql noprint;
select Diff into :CheckDiff from Trial01 where RowNum = &i.;
select ACTV_DTM into :CheckDate from Trial01 where RowNum = &i.;
create table Eval001 as select * from Trial01 where RowNum = &i.;
quit;
%if &CheckDiff. = 'N' %then %do; %put &CheckDiff.; %end;
%end;
%mend SASIsAwful;
I cannot for the life of me figure out why the %if statement did not work. Proc SQL worked fine, and I manually confirmed that it saved the value N into &CheckDiff. twelve times. Yet, for some reason, the %if statement never executed, and there was no error message. What went wrong here? Thank you.
The correct macro statement should be
%IF &CHECKDIFF = N %then ... ;
If you examine an unconditional %put, you will most likely see a log lines such as
%put INFO: &=CHECKDIFF;
--- log ---
INFO: CHECKDIFF=N;
INFO: CHECKDIFF=Y;
Richard is right.
Macrovars are always strings.
CheckDiff is just the 1-char-string N, which is different from the 3-char-string "N".
You could also write
%IF "&CHECKDIFF" = "N" %then ...
but not
%IF '&CHECKDIFF' = 'N' %then ...
because Macrovars are only replaced within double quotes, not single quotes.

SAS MACRO error: Nesting of %IF statements in open code is not supported. %IF ignored

When I execute this code, it came up with an error: Nesting of %IF statements in open code is not supported. %IF ignored.
The code is as follows:
%let Vis_Perform_Filter = VISYN^=:"Y";
data cp_ref_patient_visits;
length CP_VISIT_PERFORMED 8.;
set cp_ref_patient_visits;
/* Are all visit identifiers related to actual performed visits? */
/* if select 'Yes' */
%IF %superQ(Vis_Perform_Filter) eq %THEN %DO;
CP_VISIT_PERFORMED = 1; /* 1 OR 0 */
%END;
/* if select 'No' then apply filter condition */
%ELSE %IF %superQ(Vis_Perform_Filter) ne %THEN %DO;
if &Vis_Perform_Filter. then CP_VISIT_PERFORMED = 0;
else CP_VISIT_PERFORMED = 1;
%END;
label CP_VISIT_ID = "Internal Visit Identifier"
CP_VISIT_PERFORMED = "Visit Performed Flag";
proc sort; by SUBJID CP_PATIENT CP_VISIT_ID;
run;
And the Error is:
31 %ELSE %IF %superQ(Vis_Perform_Filter) ne %THEN %DO;
ERROR: Nesting of %IF statements in open code is not supported. %IF ignored.
ERROR: Skipping to next %END statement.
I want to know why this error happens? How to solve this error?
Thank you for your help~
If you have a lot of filters that are specified as 'data step source code snippets' in possible macro variables, you may want to code a separate macro (CODEGEN_flag_logic) to examine the filters by name and determine whether or not that source code should be emitted as part of the data step.
Example:
%MACRO CODEGEN_flag_logic (FILTER, DEFAULT=1);
%* FILTER is name of macro variable containing source code that is data step logical evaluation snippet;
%if %length(&FILTER) %then
%if %symexist (&FILTER) %then
%if %length (&&&FILTER) %then
&&&FILTER;
%else
&DEFAULT;
%else
&DEFAULT;
%else
&DEFAULT;
%MEND;
%let name_flag_logic = name =: "J"; /* extant source code */
data want;
set sashelp.class;
name_flag = %codegen_flag_logic (name_flag_logic);
name_flag_inverse = %codegen_flag_logic (name_flag_logic, DEFAULT=0);
run;
%let name_flag_logic = ; /* extant macro variable with no source code */
data want;
set sashelp.class;
name_flag = %codegen_flag_logic (name_flag_logic);
run;
%SYMDEL name_flag_logic; /* macro variable not present in session or scope */
data want;
set sashelp.class;
name_flag = %codegen_flag_logic (name_flag_logic);
run;
There will still be problems if said filters contain invalid SAS code or deal with variables expected in the PDV that are not present.
Such as
/* snippet uses homeroom (which will be noted in LOG as uninitialized */
%let name_flag_logic = homeroom = 'sunny';
/* snippet is invalid sas code and will show ERROR in the LOG */
%let name_flag_logic = busroute one of (1,2,3);
You should probably move your logic about the value of the macro variable outside of the logic of the data step to make things clearer. Looks like when the macro variable is empty you want to execute the ELSE clause of the IF statement. So just set the value to something that is false.
%let Vis_Perform_Filter = VISYN^=:"Y";
%IF %superQ(Vis_Perform_Filter) eq %THEN %DO;
%let Vis_Perform_Filter=0;
%END;
Then generating your data step code does not require any macro logic, much less any nested macro logic. You just expand the value of the macro variable where the IF statement is expecting the code to use to evaluate the condition.
data cp_ref_patient_visits;
length CP_VISIT_PERFORMED 8.;
set cp_ref_patient_visits;
/* Are all visit identifiers related to actual performed visits? */
if &Vis_Perform_Filter. then CP_VISIT_PERFORMED = 0;
else CP_VISIT_PERFORMED = 1;
label CP_VISIT_ID = "Internal Visit Identifier"
CP_VISIT_PERFORMED = "Visit Performed Flag"
;
run;
proc sort; by SUBJID CP_PATIENT CP_VISIT_ID;
run;
As this blog post reveals
%if %then %else statements are not supported in open code until SAS 9.4M5. And there are restrictions to its use.
My suggestion is to get rid of the macro functions when you are inside a data step. For example, replace
%IF %superQ(Vis_Perform_Filter) eq %THEN %DO;
CP_VISIT_PERFORMED = 1; /* 1 OR 0 */
%END;
with
if VISYN ne "Y" then CP_VISIT_PERFORMED = 1;

SAS: lookup data inside a function/subroutine / return an array

Suppose I like to do something like the following (with exemplary variable names for better readability):
take a parameter InParameter and match it to the variable MyVar1 in a dataset MyData
return all values for the variable MyVar2 for the filtered observations
from a subroutine/function
that i can use inside proc sql/datastep
This is what I got so far (clearly not working):
proc fcmp outlib=work.funcs.MyFunction;
function MyFunction(InParameter $);
array MyArray ... ; /* Here: Create an array with something like SELECT MyVar2 FROM MyData WHERE MyVar1 = Inparameter */
return(MyArray{});
endsub;
;
quit;
options cmplib=work.funcs;
data MyOutput;
set Somedata;
if MyVar2 in MyFunction("H20") then output;
run;
In short:
can data in datasets be accessed from inside a function/subroutine?
can a function/subroutine return an array?
Thanks for your help!
We created a utility macro called %ds2list() that will perform your desired process. It doesn't use an array statement but it achieves the same result.
The macro simply returns values from a dataset in a list format. Here's an example of calling it:
%put %ds2list(iDs=sashelp.class, iField=name, iQuote=1);
This would return:
'Alfred','Alice','Barbara','Carol','Henry','James','Jane','Janet','Jeffrey','John','Joyce','Judy','Louise','Mary','Philip','Robert','Ronald','Thomas','William'
The default behavior for %ds2list() is to comma separate the returned values but it is very flexible. You can change the delimiter to a value of your choice (or no delimiter), you can turn the quotes on or off, or change them from single to double quotes, and you can provide any dataset options you would normally use on a set statement such as a where=() statement.
Additionally because the macro is pure macro code you can use this literally anywhere in SAS. In any proc/data/macro you like. We use it extensively for calls to ODBC passthrough when we have a large list of IDs we want to be returned.
Here's an example of how you could use it. First create a table that will contain values to compare against the list values:
data keep;
input name $;
datalines;
Alfred
Carol
Janet
run;
Iterate over the values we want to check against the list:
data want;
set keep;
if name in (%ds2list(iDs=sashelp.class, iField=name, iQuote=1, iDsOptions=where=(sex='F'))) then do;
output;
end;
run;
Returns:
Obs name
=== =====
1 Carol
2 Janet
You can see Alfred was excluded from the result because he was filtered out by the where=() clause.
Here is the macro, I suggest putting it in your macro autocall library:
/***************************************************************************
** 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
**
*****************************************************************************/
%macro ds2list(iDs=, iField=, iDsOptions=, iDelimiter=%str(,), iQuote=0, iQuoteChar=single);
%local dsid pos rc result cnt quotechar value;
%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 = %qtrim(&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;
Not sure that a function would work with the IN operator. You might need to wrap the function call with a macro to generate the proper syntax. In which case why not just make a macro to begin with?
Here is generic macro to extract the values from a variable in a dataset.
%macro varlist
/*----------------------------------------------------------------------
Generate list of values from dataset
----------------------------------------------------------------------*/
(dataset /* Input dataset */
,variable /* Variable Name */
,quote=1 /* Add quotes around values? 1=Single 2=Double */
,comma=1 /* Add comma between values? */
,paren=1 /* Add parentheses around results? */
);
%local did sep &variable ;
%if &paren=1 %then (;
%let did=%sysfunc(open(&dataset));
%syscall set(did);
%do %while(0=%sysfunc(fetch(&did)));
%let &variable=%qsysfunc(trim(%superq(&variable)));
%if &quote=1 %then &sep.%sysfunc(quote(&&&variable,%str(%')));
%else %if &quote=2 %then &sep.%sysfunc(quote(&&&variable));
%else &sep.&&&variable;
%if &comma=1 %then %let sep=,;
%end;
%let did=%sysfunc(close(&did));
%if &paren=1 %then );
%mend varlist;
Example calls:
%put %varlist(sashelp.class,name);
%put %varlist(sashelp.class(where=(sex='M')),age,quote=0,comma=0);
So in your case you might use it like this:
data MyOutput;
set Somedata;
where MyVar2 in %varlist(Mydata(where=(MyVar1="H20")),MyVar2) ;
run;
You are better off with a macro.
%macro subset(inParameter, indata, outdata);
proc sql noprint;
create table &outdata as
select * from &indata
where myVar2 in (select distinct myVar2 from myData where myVar1 = "&inParameter);
quit;
%mend;
%subst(H20,Somedata,MyOutput);

Controlling program flow - sas

Below is the code to execute a set of data-steps based on the value of the increment variable "i". Since I have assigned the value of i to 1 (numeric and not character value). Ideally the first data-step block need to execute, but in the below case the second data-step block is executing.
%put &i. ; prints 1 in to the log window.
%macro DSN;
%let i = 1 ;
data new_DSN;
run;
%if i = 1 %then %do;
data Dummy ;
run;
data DUMMY_ ;
set DUMMY new_DSN ;
run;
%end;
%else %if i ^= 1 %then %do ;
data DUMMY_ ;
set DUMMY_ new_DSN ;
run;
%end;
%mend DSN;
%DSN;
Your IF statement is not calling &I macro variable, but simply comparing string I to 1. This also explains why your second loop running because technically speaking string "I" is not equal to "1". You just need to put ampersand in front of I in both %IF statements. I also put two %PUT statements to easier see where code is running. See below:
%macro DSN;
%let i = 1 ;
data new_DSN;
run;
%if &i = 1 %then %do;
%PUT First Loop Run;
data Dummy ;
run;
data DUMMY_ ;
set DUMMY new_DSN ;
run;
%end;
%else %if &i ^= 1 %then %do ;
%PUT Second Loop Run;
data DUMMY_ ;
set DUMMY_ new_DSN ;
run;
%end;
%mend DSN;
%DSN;

SAS Global date comparison

I'm trying to do a date comparison but I'm not getting the correct results. Does anyone know what's going on?
%macro ttt;
%let check_start = 28APR2014;
%if "&check_start."d < "25may2014"d %then %let true = 1;
%else %if "&check_start."d > "25may2014"d %then %let true = 2;
%put &true;
%mend;
%ttt;
14 %macro ttt;
15 %let check_start = 28APR2010;
16 %if "&check_start."d < "25may2014"d %then %let true = 1;
17 %else %if "&check_start."d > "25may2014"d %then %let true = 2;
18 %put &true;
19 %mend;
20 %ttt;
true = 2
Macro-variable true should equal 1
You need to use %sysevalf() to evaluate the comparison in this case. The following works.
%macro ttt;
%let check_start = 28APR2015;
%if %sysevalf("&check_start"d < '25may2014'd) %then %let true=1;
%else %if %sysevalf("&check_start."d > '25may2014'd) %then %let true=2;
%put &true.;
%mend;
%ttt;
Reeza has provided a good solution but I thought I'd add a few suggestions as well.
The problem you are having is the reason I recommend never using date literals when working in the macro language. Instead, of date literals (i.e. "01jan2000"d) I recommend using macro variables that contain date values (i.e. %let start_of_21st_century = %sysfunc(mdy(1,1,2000)); ). By using macro variables, not only do you avoid your above issue, but you also get the benefit of being able to self-document your code.
Currently I have no idea what significance the 25th May 2014 has in your code, but if you had this line:
%let product_launch_date = %sysfunc(mdy(5,25,2014));
... then it would be clear to anyone reading it what the significance is.
Your code would then become:
%macro ttt;
%local check_start compare_date;
%let check_start = %sysfunc(mdy(4,28,2014));
%let compare_date = %sysfunc(mdy(5,25,2014));
%if &check_start < &compare_date %then %let true = 1;
%else %if &check_start > &compare_date %then %let true = 2;
%put &true;
%mend;
There's still a few more things I'd consider changing. One thing I noticed is that if the 2 date values are equal, than true will not be assigned a value. So that should probably be remedied.
Also, in SAS, the typical concept of true/false is typically represented as follows:
A value of zero represents FALSE
Any non-zero number (including negatives) represents TRUE
So having a macro variable named true with either a value of 1 or 2 (both values would normally represent a value of true) may be confusing to some. I'd consider either renaming the macro variable, or using values of 0, and 1 (or other non-zero number).
Incorporating all of this, the macro would become something like:
%macro check_dates;
%local check_start compare_date;
%let check_start = %sysfunc(mdy(4,28,2014));
%let compare_date = %sysfunc(mdy(5,25,2014));
%let check_start_compared_higher = &check_start > &compare_date;
%if &check_start_compared_higher %then %do;
%put It was higher =) ;
%end;
%else %do;
%put It was equal to or lower =( ;
%end;
%mend;
%check_dates;
A few comments on the final macro... The macro variable named true has been replaced with a more descriptive variable named check_start_compared_higher. Because we just need a boolean value stored in it, we can simply assigning it the result of evaluating the expression &check_start > &compare_date which will return either a 0 (if false) or a 1 (if true). This is easier to read than using %if...%else... statements to do an assignment, as it is immediately clear that the line of code is simply performing an assignment and nothing more.
The line %if &check_start_compared_higher %then %do; shows how we can use the newly saved value to control program flow. Because the value in &check_start_compared_higher resolves to either TRUE or FALSE we can easily use it this way to make easy-to-read if-statements.