I came across SAS code recently that looks something like this:
%if var_name ~= %then %do;
flag = 1;
%end;
I understand that ~= means "not equal", but there appears to be nothing here for the variable to be compared to. Can someone shed any light on this syntax?
I've ruled out the possibility that this is shorthand for identifying missing observations: the flag is generated regardless of whether var_name contains any missing observations. That being said, it does the exact same thing as the code that you would think would actually do this:
%if var_name ~= . %then %do;
flag = 1;
%end;
The above also generates a flag with value 1 for all observations.
Any help on this greatly appreciated as I am quite new to SAS!
Bestimate: The macro expression is emitting flag=1; as an unconditional DATA step statement .
"Coming across SAS code" can be anywhere on the continuum of a singularly rewarding experience to a journey into a deep dark place.
The snippet
%if var_name ~= . %then %do;
flag = 1;
%end;
is construct consistent with someone who is learning macro and does not yet grok the scopes and environments within a SAS session. Macro variables and statements do not mingle with running data step variables and statements. Macro programming typically controls what is eventually seen as the DATA or PROC step source code that needs to be run.
There can be legitimate reasons for the snippet and therein starts your journey.
%IF expression %THEN statement; involves the resolution of a macro logical expression.
The expression is implicitly resolved and evaluated to be zero (false) or non-zero (true). Expressions that can not be resolved down to a non-missing numeric value at macro evaluation time will log an ERROR:
NOTE: Macro evaluation time is long gone by the time the SAS executor has compiled and is executing the DATA Step. SAS Documentation is pretty awesome, use it!.
Your var_name ~= expression is always true.
%put NOTE: %nrstr(%eval(var_name ~=)) resolves to %eval(var_name ~=);
----
NOTE: %eval(var_name ~=) resolves to 1
Because the %IF expression always resolves to true the %THEN statement is always resolved and emitted as source code to be consumed by the SAS executor.
So in your case the source code flag = 1; is emitted, ostensibly as part of a DATA step in which the flag assignment is unconditional.
Many times the statement is another macro expression that does not emit anything and instead performs an action that affects the macro state at the current macro scope -- For example %IF &variable=&target %THEN %let target_met=1;.
The statements around the one you noticed are really clues to whether the %IF is correct and what it should be. What could it be?
Does the data set be processed actually have a column named var_name ?Maybe you are dealing with metadata output by Proc CONTENTS, SQL DICTIONARY.COLUMNS or working in a framework that uses control data for generating statements.
A goofup wherein the %if - %then should really be a data step if -then and the var_name should have been replaced with an actual variable name found in the data set being processed.
Working in a code generating framework where non-empty symbols representing data step variables are used to generate data step if-then statements
The code is the work of a madman, mad genius, or village idiot.
Happy coding!
Related
As part of my work, I am going through this SAS code. I have never worked on SAS before. Could anybody explain the usage of '%' in front of the SAS lines in the code below?
%if &i>0 %then %do;
or
%put ##### calling formula;
%formula();
% signs indicate macros or macro code. Macros in SAS are similar to functions in other programming languages, but not quite. You can treat them like functions though. They deal exclusively with text and only text.
The macro facility exists to handle more generalized problems. Things in SAS are done in PROCs and the DATA Step. Each PROC and DATA Step is like its own little self-contained environment - stuff that happens in there stays in there. The Macro facility gives you tools to do things like conditionally call PROCs, the DATA Step, or system options.
I highly recommend taking one of the free training courses on SAS programming. If you want to get a jump start on Macro language, start with this free course on Coursera.
To help get you started, here's what this code is doing, line-by-line.
%if &i>0 %then %do;
If the macro variable, i, is greater than 1, run some code. All macro variables start with &. The value of the macro variable i can be reviewed by typing %put &i.
%put ##### calling formula;
%put writes a line to the log in open code. This is likely being used to help debug things.
%formula();
This is a macro function that holds some code and runs it. It has no arguments. Macro functions are created with the following syntax:
%macro myMacro();
<macro or SAS code here>;
%mend;
% is the key symbol to invoke a macro. If we wanted to invoke myMacro, we can do so by prefixing it with a %:
%myMacro;
I have implemented an IF-statement in a SAS program which basically checks the validity of a year comparison. If the IF-statement is TRUEsome further variables are initialized and computed, if IF-statement is FALSE nothing happens.
However, I just realized that no matter if the IF-statement is TRUE or FALSE the additional variables will be initialized (though as MISSING in the FALSE case) and displayed in the ODS. Why is that? Is there a direct way to solve this? I could of course use a conditional drop-statement but this seems tedious to always add this.
data test;
value = 1;
if value > 2019 then a = 1;
/*
if value < 2019 then
do;
drop a;
end;
*/
run;
EDIT: Again I just realized that the drop-statement will also always drop no matter if TRUE/FALSE.
Solution but no idea why it works:
data test;
%let value = 1;
%if &value. > 2019 %then %do; a = 1; %end;
run;
While digging around I found this sas-community post. My problem seems to basically stem from the difference of %IF vs IF. Applying the MACRO %IF it works. The post seems to be a bit out date, as opposed to the post one can now use MACRO %IF outside of a MACRO.
This is the difference between compiling and running a datastep. When a datastep is compiled (before it's actually run), SAS figures out what variables it's going to need. Since the column a is on the left-hand side of an equation, it gets added to the dataset. Remember, at this point, SAS has no idea what data you're going to be putting into it; you might need that a column, you might not. That's why you can't conditionally add/remove columns during run-time; the columns need to be known at compilation.
In the second case, as Tom pointed out, the macro expressions are being evaluated before the datastep is compiled. From SAS' point of view, your code:
data test;
%let value = 1;
%if &value. > 2019 %then %do; a = 1; %end;
run;
Just evaluates to this:
data test;
run;
On the other hand, this code:
data test;
%let value = 2020;
%if &value. > 2019 %then %do; a = 1; %end;
run;
Would evaluate to this:
data test;
a = 1;
run;
MACRO code and SAS code are two different systems. The macro processor checks the code first and interprets any macro code (looking for % and & triggers). The resulting text then continues on and is evaluated as SAS code.
So in your second case the macro processor generates different SAS code based on the value of the macro variable.
Also note that your cases are testing different things. In the macro code you are testing the value of a macro variable. In the data step code you are testing the value of dataset variable (for a particular observation).
I have several programs in one SAS project, i.e program A -> program B ->.... I want to build dependency between programs using macro variables.
Program A will process few data step and procs. If ANY procedure in program A executes with errors, I would like to run program C. Otherwise run program B.
This seems to be tricky since syserr resets at each step boundary. If first data step in program A executes with error and the rest don't, then at the end of program A, syserr is still 0. I need macro variable value to be something other than 0 once error happens and the value can keep till program ends.
If the program dependency is based on other criteria (say values), the user-defined macro variables can handle that. For something related to system errors, I think SAS already has something can do the trick.But I can't find anything else except syserr, which doesn't seem to help.
Note: I find this SAS stop on first error. But basically it's to check the error status after each data step. That sounds crazy if program A contains 50+ data steps.
Easy - just use syscc!
SYSCC is a read/write automatic macro variable that enables you to
reset the job condition code and to recover from conditions that
prevent subsequent steps from running.
See documentation, but I guess you will be looking for something like:
%if &syscc > 4 %then %do;
%inc "/mypath/pgmB.sas";
%end;
%else %do;
%inc "/mypath/pgmA.sas";
%end;
The highest value of syscc is retained across step boundaries, always with an integer to represent the error level. Example values:
The values for SYSCC are:
0 is no errors no warnings
4 is warnings
greater than 4 means an error occurred
Note that there are some things it won't catch, but to improve it's effectiveness you can use:
options errorcheck=strict;
Finally - you mention 'sas project', if by this you mean you are using Enterprise Guide then please be aware of the advice in this usage note.
You could define a macro that kept track of the error status and run this after each step. The macro would look something like this:
%macro track_err;
%global err_status;
%let err_status = %sysfunc(max(&err_status, &syserr ne 0));
%mend;
Usage example below. First initialize the value to track the overall error status. The first datastep will fail, the second will run successfully and the final value of err_status will be 1.
%let err_status = 0;
data oops;
set sashelp.doesnt_exist;
run;
%track_err;
data yay;
set sashelp.class;
run;
%track_err;
%put &=err_status;
Final output:
ERR_STATUS=1
As far as 'sounding crazy to check the status after each step'... well SAS doesn't provide something for the exact requirement you have, so the only way to do it is literally to have something check after each step.
EDIT: Correction - looks like the syscc approach mentioned in RawFocus's answer actually shows there IS something that does this in SAS.
If you want the check to 'blend in' more with the code, then consider replacing your run and quit statements with a macro that performs the run/quit, then checks the status all in one. It will result in slightly cleaner code. Something like this:
%macro run_quit_track;
run;quit;
%global err_status;
%let err_status = %sysfunc(max(&err_status, &syserr ne 0));
%mend;
%let err_status = 0;
data oops;
set sashelp.doesnt_exist;
%run_quit_track;
data yay;
set sashelp.class;
%run_quit_track;
%put &=err_status;
Use one macro, call it runquitA. call this macro at the end of each proc sql or proc data in the place of quit; and run;
Example:
/*Program A*/
%macro runquitA;
; run; quit;
%if &syserr. ne 0 %then %do;
/*Call Program C*/
%end;
%mend runquitA;
proc sql;
create table class1 as
select * from sashelp.class;
%runquitA;
data class2;
set sashelp.class;
%runquitA;
/*Call Program B*/
/*end of Program A*/
Please consider this sample SAS macro code:
%MACRO reports;
%IF &SYSDAY = Monday %THEN %DO;
%END;
%MEND reports;
Does every single word inside the macro need to be prefixed with a %? What exactly does the % sign mean?
% is a macro trigger, along with &. It identifies the next symbol(s) as part of a macro language element. This might be a macro call (%reports();), a macro statement (%if), a macro comment (%*), or other macro language elements.
Understanding how the SAS macro language works is pretty important to understanding the difference here. %IF for example is instructing the SAS macro processor to do something. IF is regular SAS code that will be put into the SAS data step (or whatever). Spend some time understanding what the macro language is doing - what the point of it is entirely - to fully understand that.
And, as with many things in SAS, Ian Whitlock can explain it better than I can.
The % symbol indicates that it is macro logic, no datastep logic.
Macro logic is executed before compilation, just like pre-compiler logic in C++. Fro instance
%MACRO reports ;
data lastWorkingDayData;
set allData;
%IF &SYSDAY = Monday
%THEN %DO ;
if transactionDate ge "&SYSDATE."d -3 then output;
%END ;
%ELSE %DO ;
if transactionDate ge "&SYSDATE."d -1 then output;
%END ;
RUN ;
/* your printing logic comes here */
%MEND reports ;
%reports;
will, if you run it today be converted before it is even compiled to
data lastWorkingDayData;
set allData;
if transactionDate ge "&SYSDATE."d -3 then output;
RUN ;
/* your printing logic comes here */
before it is even compiled. To understand it better, start your code with option mprint; and inspect your log
I am new to SAS and having a hard time figuring out when should the simple If-Then-else and when should %IF-%THEN-%ELSE should be used. As an example code below:
%let inFile = %scan(&sysparm, 1, " ");
%macro read_data(infile);
data want;
infile "&infile" LRECL=1000;
retain fdate;
if _n_ = 1 then do;
input Agency $ Status $ Num $ fdate sdate;
end;
else do;
%if fdate < 20130428 %then
input
#1 poolno $6.
#7 factor 9.8 #;
%else
input
#1 rectype $1
#3 poolno $6.
#9 factor 9.8 #;
#18 pfactor 9.8;
output;
end;
drop Agency Status Num sdate;
run;
proc print data=want;
run;
%mend read_data;
%read_data(&inFile);
I am trying to get the first line(header) and taking the parameter fdate. Based on the value of this parameter, I am parsing the subsequent input lines differently.
But this does not seem to work, as only the second input part runs (always getting parameter 'rectype' in output).
Any suggestions as what i might be doing wrong?
I see you have C++ as one of your tags, and that you are just getting started with SAS. So I'll try to provide an answer specific to your background.
The easiest way to understand the distinction between SAS Macro commands and the commands in the DATA step or in several procs with the same name, like %if vs. if, is to think of SAS Macro commands as equivalent to C/C++ pre-processor (CPP) directives. CPP and SAS MAcro are both macro languages, and although they are not exactly the same kind of language, they share two important and initially confusing characteristics: they are text processors; and they are executed as a separate step before the main code is processed.
There are places where this approximation breaks down, but as a beginner to SAS with background in C/C++, it is a good place to start.
The macro statement %if is compiled before any data step statement. That means you are generally not able to use a data step variable in your logic expression. When the macro processor is compiling the macro statement, data step variable doesn't exist yet.
In the example above, the %IF condition is based on a datastep variable/value. This should indicate it can be achieved using a datastep 'if' rather than a %IF.
You have already received an answer to this in your previous question, https://stackoverflow.com/a/15341502/108797
If you do %if fdate < 20130428 SAS compares literals fdate and 20130428 not
the value od fdate and 20130428.
If you had a macro variable named fdate you would do %if &fdate < 20130428.
In your case fdate is a variable in a dataset, so use if not %if, but it seems like you are trying to create a data step with a macro so just using if will probably not work in this case (depends on what are you trying to get).