Using Toggle Statements in SAS - if-statement

I am attempting to create a program that toggles certain sections of the code on or off based on user input. The code below should only run when the prog1 variable is set equal to Y. However, my log is showing that the code runs no matter what. Does anyone know what is going on?
Code:
%let prog1 = Y;
%let prog2 = N;
data _null_;
if "&prog1." = "Y" then do;
%findit(&file1.);
%findit(&file2);
end;
run;
data _null_;
if "prog2." = "Y" then do;
%findit(&file3.);
end;
run;
Log:

It is doing what you told it to do. The macro references and macro code will be evaluated first. Then any SAS code that the macro references generate will by processed by SAS. So you have written a DATA step that will conditionally skip over the SAS code that the macros generate. But the macros themselves will always run.
If you code the main program as a macro instead of open SAS code then you can add macro logic like %IF to conditionally generate the macro calls.
Or for this simple example you could use CALL EXECUTE() or other code generation methods to control the generation of the macro calls. That way SAS will never see the macro calls in the input stream if the condition is false.
data _null_;
if "&prog1." = "Y" then do;
call execute('%nrstr(%findit)(&file1.)');
call execute('%nrstr(%findit)(&file2.)');
end;
run;

You have some mistake in your code. (use of macro function into a dataset, misuse of & (ampercent) in the call of your macro-variable).
1) Always use the & (ampercent) to call a macro-variable
2) Add a point like &path. when it's necessary, if there is another string or macro-variable following the &path macro-variable.
3) Prefer to make your check using macro function with %if,%then, ect.
You should make your program more macro oriented like that :
%let prog1 = Y;
%let prog2 = Y;
%macro check();
%if "&prog1." = "Y" %then %do;
%put execute 1;
%findit(&file1);
%findit(&file2);
%end;
%if "&prog2." = "Y" %then %do;
%put execute 2;
%findit(&file3);
%end;
%mend;
%check;
It will work now,
Regards,

Your second if statement is checking "prog2." not "&prog2." and in your log the macro variable "&prog3." is the one getting resolved instead of &prog1. and &prog2.
Try adding this to your code which prints all the user macro variables to the log.
%put _user_;

Related

What will be the output of this %macro function in SAS and how to display the output in sas studio

%macro segm1;
data _null_;
%do i=0 %to 8;
call symput("yyyymm_i",put(intnx('month',today(),-1,'b'),yymmn6.));
%end;
%mend;
%segm1;
run;
What will be the output and how to get/dispaly/view output of this macro code in sas studio?
Since a macro is used to generate SAS code to view the output of macro set the MPRINT option before running it. You will see that the macro generates a data statement and 8 call symput statements.
There are a lot of problems with that code.
Uses %DO loop where it should be using a DO loop.
Creates the same macro variable over and over.
Starts a data step, but does not end it. Was this on purpose? Why?
Creates LOCAL macro variables that will disappear when the macro finishes.
Uses older less functional call symput() function instead of call symputx().
If you want to create 8 macro variables just use a normal DO loop. No need for a macro. Use the value of the loop variable to change the name of the macro variable generated and the month the result represents.
data _null_;
do i=0 to 8;
call symputx(cats('yyyymm_',i),put(intnx('month',today(),-i,'b'),yymmn6.));
end;
run;
Which will create a series of macro variables named YYYYMM_0 to YYYYMM_8 with six digit strings like 202204 , 202203 , ... representing the current month back to eight months ago
If you did want to run that inside a macro and have the macro variables it creates available after the macro ends then set the optional third parameter to call symputx() to the string 'G' so that they are defined in the global symbol table instead of the local symbol table.
You can print individual macro variables to the log with the %put statement, like %put &yyyymm_i;
You can print all macro variable with %put _all_;, or if you are only interested in variables you created yourself: %put _user_; (or within a macro %put _local_;)
By the way, your code is wrong, try this
%macro segm1;
data _null_;
%do i=0 %to 8;
call symput("yyyymm_&i", put(intnx('month',today(),-1,'b'),yymmn6.));
%end;
%mend;
%segm1;
run;
%put _user_;

Dynamically create the "KEEP" List in SAS

I am trying to create a keep list dynamically. Say:
%MACRO TEST(A=,B=,OUT_VAR=,KEEP_VAR=);
&OUT_VAR=MAX(&A,&B);
%IF &KEEP_VAR = 'Y' %THEN VAR_LIST=%SYSFUNC(CATS(VAR_LIST,&OUT_VAR));
%PUT VAR_LIST;
%MEND;
DATA ABC (keep = VAR_LIST);
LENGTH VAR_LIST $100.;
RETAIN VAR_LIST '';
%TEST(A=1,B=3,OUT_VAR=FIRS,KEEP_VAR='Y');
%TEST(A=2,B=4,OUT_VAR=SEC,KEEP_VAR='Y');
%TEST(A=3,B=5,OUT_VAR=THIR,KEEP_VAR='N');
RUN;
I have a datastep in which i am creating several variables calculated by the macro code.
I want to create a dynamic list of these output variables and then use it in the keep statement.
The above code does not seem to work, can someone suggest what am I missing here.
A running data step is source code that has already been compiled. You are trying to change the source while it is running -- that is not going to happen. This is a symptom of mixing scopes and contexts between macro code and generated code.
However, the TEST macro is generating source and thus can either
maintain state information for generating a KEEP source code statement after the macros are invoked.
generate an additional KEEP statement per TEST invocation
In general, macro only parameter values are already 'strings', and do not need to be quoted.
%MACRO TEST(A=,B=,OUT_VAR=,KEEP_VAR=);
&OUT_VAR=MAX(&A,&B);/* generates data step source code*/
%IF &KEEP_VAR = 'Y' %THEN
%LET VAR_LIST=&VARLIST &OUT_VAR;
%MEND;
* must occur before sequence of TEST invocations;
%global VARLIST; * globals generally want to be avoided, next version avoids it;
%let VARLIST =;
DATA ABC;
%TEST(A=1,B=3,OUT_VAR=FIRS,KEEP_VAR='Y');
%TEST(A=2,B=4,OUT_VAR=SEC,KEEP_VAR='Y');
%TEST(A=3,B=5,OUT_VAR=THIR,KEEP_VAR='N');
KEEP &VARLIST;
RUN;
%symdel VARLIST;
I hope this is a learning exercise -- The above pattern of coding has a macro generate code that would suffice as two normal non-macro data step statements do. Too much macroification can be obfuscating.
Additionally, the output ABC might not be what you expect. There will be one row with A=3, B=5, FIRS=4, SEC=6 and no THIR=8. So you have also some mixed up row and column data concepts.
Here is a cleaner alternative in which TEST generates the KEEP
%MACRO TEST(A=,B=,OUT_VAR=,KEEP_VAR=);
&OUT_VAR=MAX(&A,&B);/* generates data step source code*/
%IF %upcase(&KEEP_VAR) = Y %THEN %STR(KEEP &OUT_VAR;);
%MEND;
DATA ABC;
%TEST(A=1,B=3,OUT_VAR=FIRS,KEEP_VAR=Y);
%TEST(A=2,B=4,OUT_VAR=SEC,KEEP_VAR=Y);
%TEST(A=3,B=5,OUT_VAR=THIR,KEEP_VAR=N);
RUN;
The macro code still has to rely on the caller to specify a proper KEEP_VAR parameter Y or not Y.
An alternative is to push the Y/N KEEP concept into two parameters, use of one parameter over the other discriminates the KEEP.
%MACRO TEST(A=,B=,KEEP_VAR=,TEMP_VAR=);
%IF %length (&KEEP_VAR) %then %do;
&KEEP_VAR
%end;
%else %do;
&TEMP_VAR
%end;
MAX(&A,&B);
%IF %length (&KEEP_VAR) %then %do;
KEEP &KEEP_VAR;
%end;
%MEND;
DATA ABC;
%TEST(A=1,B=3,KEEP_VAR=FIRS);
%TEST(A=2,B=4,KEEP_VAR=SEC);
%TEST(A=3,B=5,TEMP_VAR=THIR);
RUN;
For me, a widely used macro should have clarity of invocation. The black box of the macro implementation can strive to be as clean and efficient but does not have to be.
It is not clear what you are trying to do, but you can use multiple KEEP statements if want.
data want ;
x=1;
keep x;
y=2;
z=3;
keep z;
run;
Although for your application it looks like you might want to generate a DROP statement instead.
%MACRO TEST(A=,B=,OUT_VAR=,KEEP_VAR=Y);
&OUT_VAR=MAX(&A,&B);
%IF (&KEEP_VAR ^= Y) %THEN %DO;
drop &out_var ;
%end;
%MEND;
DATA ABC ;
%TEST(A=1,B=3,OUT_VAR=FIRST)
%TEST(A=2,B=4,OUT_VAR=SECOND)
%TEST(A=3,B=5,OUT_VAR=THIRD,KEEP_VAR=N)
RUN;

SYSERR Automatic Macro Variable

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*/

SAS - How to return a value from a SAS macro?

I would like to return a value from a SAS macro I created but I'm not sure how. The macro computes the number of observations in a dataset. I want the number of observations to be returned.
%macro nobs(library_name, table_name);
proc sql noprint;
select nlobs into :nobs
from dictionary.tables
where libname = UPCASE(&library_name)
and memname = UPCASE(&table_name);
quit;
*return nobs macro variable;
&nobs
%mend;
%let num_of_observations = %nobs('work', 'patients');
Also, I would like the &nobs macro variable that is used within the macro to be local to that macro and not global. How can I do that?
I'll answer the core question Bambi asked in comments:
My main concern here is how to return a value from a macro.
I'm going to quibble with Dirk here in an important way. He says:
A SAS macro inserts code. It can never return a value, though in some cases you can mimic functions
I disagree. A SAS macro returns text that is inserted into the processing stream. Returns is absolutely an appropriate term for that. And when the text happens to be a single numeric, then it's fine to say that it returns a value.
However, the macro can only return a single value if it only has macro statements in addition to that value. Meaning, every line has to start with a %. Anything that doesn't start with % is going to be returned (and some things that do start with % might also be returned).
So the important question is, How do I return only a value from a macro.
In some cases, like this one, it's entirely possible with only macro code. In fact, in many cases this is technically possible - although in many cases it's more work than you should do.
Jack Hamilton's linked paper includes an example that's appropriate here. He dismisses this example, but that's largely because his paper is about counting observations in cases where NOBS is wrong - either with a WHERE clause, or in certain other cases where datasets have been modified without the NOBS metadata being updated.
In your case, you seem perfectly happy to trust NOBS - so this example will do.
A macro that returns a value must have exactly one statement that either is not a macro syntax statement, or is a macro syntax statement that returns a value into the processing stream. %sysfunc is an example of a statement that does so. Things like %let, %put, %if, etc. are syntax statements that don't return anything (by themselves); so you can have as many of those as you want.
You also have to have one statement that puts a value in the processing stream: otherwise you won't get anything out of your macro at all.
Here is a stripped down version of Jack's macro at the end of page 3, simplified to remove the nlobsf that he is showing is wrong:
%macro check;
%let dsid = %sysfunc(open(sashelp.class, IS));
%if &DSID = 0 %then
%put %sysfunc(sysmsg());
%let nlobs = %sysfunc(attrn(&dsid, NLOBS));
%put &nlobs;
%let rc = %sysfunc(close(&dsid));
%mend;
That macro is not a function style macro. It doesn't return anything to the processing stream! It's useful for looking at the log, but not useful for giving you a value you can program with. However, it's a good start for a function style macro, because what you really want is that &nlobs, right?
%macro check;
%let dsid = %sysfunc(open(sashelp.class, IS));
%if &DSID = 0 %then
%put %sysfunc(sysmsg());
%let nlobs = %sysfunc(attrn(&dsid, NLOBS));
&nlobs
%let rc = %sysfunc(close(&dsid));
%mend;
Now this is a function style macro: it has one statement that is not a macro syntax statement, &nlobs. on a plain line all by itself.
It's actually more than you need by one statement; remember how I said that %sysfunc returns a value to the processing stream? You could remove the %let part of that statement, leaving you with
%sysfunc(attrn(&dsid, NLOBS))
And then the value will be placed directly in the processing stream itself - allowing you to use it directly. Of course, it isn't as easy to debug if something goes wrong, but I'm sure you can work around that if you need to. Also note the absence of a semi-colon at the end of the statement - this is because semicolons aren't required for macro functions to execute, and we don't want to return any extraneous semicolons.
Let's be well behaved and add a few %locals to get this nice and safe, and make the name of the dataset a parameter, because nature abhors a macro without parameters:
%macro check(dsetname=);
%local dsid nlobs rc;
%let dsid = %sysfunc(open(&dsetname., IS));
%if &DSID = 0 %then
%put %sysfunc(sysmsg());
%let nlobs = %sysfunc(attrn(&dsid, NLOBS));
&nlobs
%let rc = %sysfunc(close(&dsid));
%mend;
%let classobs= %check(dsetname=sashelp.class);
%put &=classobs;
There you have it: a function style macro that uses the nlobs function to find out how many rows are in any particular dataset.
What is the Problem writing function-like macros?
i.e. macros you can use as%let myVar = %myMacro(myArgument)
You can use your user written macro as if it were a function if all you do is
calling some %doSomething(withSometing) like macro functions
assign values to macro variables with a %let someVar = statement
"return" your result, typically by writing &myResult. on the last line before your %mend
As soon as you include a proc or data step in your macro, this does not work any more
Luckily, %sysFunc() comes to the rescue, so we can use any data step function
This includes low level functions like open, fetch and close which can even access your data
nerdy people can do quite a lot with it, but even if you are nerdy, your boss will seldom give you the time to do so.
How do we solve this?, i.e. which building blocks do I use to solve this?
proc fcmp allows packaging some data step statements in a subroutine or function
This function, meant for use in a data step, can be used within %sysfunc()
Within this function you can call run_macro to execute any macro IN BACKGROUND IMMEDIATELY
Now we are ready for the practical solution
Step 1: write a helper macro
with no parameters,
using some global macro variables
"returning" its result in a global macro variable
I know that is bad coding habit, but to mitigate the risk, we qualify those variables with a prefix. Applied to the example in the question
** macro nobsHelper retrieves the number of observations in a dataset
Uses global macro variables:
nobsHelper_lib: the library in which the dataset resides, enclosed in quotes
nobsHelper_mem: the name of the dataset, enclosed in quotes
Writes global macro variable:
nobsHelper_obs: the number of observations in the dataset
Take care nobsHelper exists before calling this macro, or it will be ost
**;
%macro nobsHelper();
** Make sure nobsHelper_obs is a global macro variable**;
%global nobsHelper_obs;
proc sql noprint;
select nobs
into :nobsHelper_obs
from sashelp.vtable
where libname = %UPCASE(&nobsHelper_lib)
and memname = %UPCASE(&nobsHelper_mem);
quit;
%* uncomment these put statements to debug **;
%*put NOTE: inside nobsHelper, the following macro variables are known;
%*put _user_;
%mend;
Step 2: write a helper function;
**Functions need to be stored in a compilation library;
options cmplib=sasuser.funcs;
** function nobsHelper, retrieves the number of observations in a dataset
Writes global macro variables:
nobsHelper_lib: the library in which the dataset resides, enclosed in quotes
nobsHelper_mem: the name of the dataset, enclosed in quotes
Calls the macro nobsHelper
Uses macro variable:
nobsHelper_obs: the number of observations in the dataset
**;
proc fcmp outlib=sasuser.funcs.trial;
** Define the function and specity it should be called with two character vriables **;
function nobsHelper(nobsHelper_lib $, nobsHelper_mem $);
** Call the macro and pass the variables as global macro variables
** The macro variables will be magically qouted **;
rc = run_macro('nobsHelper', nobsHelper_lib, nobsHelper_mem);
if rc then put 'ERROR: calling nobsHelper gave ' rc=;
** Retreive the result and pass it on **;
return (symget('nobsHelper_obs'));
endsub;
quit;
Step 3: write a convenience macro to use the helpers;
** macro nobs retrieves the number of observations in a dataset
Parameters:
library_name: the library in which the dataset resides
member_name: the name of the dataset
Inserts in your code:
the number of observations in the dataset
Use as a function
**;
%macro nobs(library_name, member_name);
%sysfunc(nobsHelper(&library_name, &member_name));
%* Uncomment this to debug **;
%*put _user_;
%mend;
Finally use it;
%let num_carrs = %nobs(sasHelp, cars);
%put There are &num_carrs cars in sasHelp.Cars;
Data aboutClass;
libname = 'SASHELP';
memname = 'CLASS';
numerOfStudents = %nobs(sasHelp, class);
run;
I know this is complex but at least all the nerdy work is done.
You can copy, paste and modify this in a time your boss will accept.
;
A SAS macro inserts code. It can never return a value, though in some cases you can mimic functions, usually you need a work around like
%nobs(work, patients, toReturn=num_of_observations )
** To help you understand what happens, I advice printing the code inserted by the macro in your log: ;
options mprint;
We pass the name of the macro variable to fill in to the macro, I find it most practical to
not require the user of my macro to put quotes around the libary and member names
make the name of the variable a named macro variable, so we can give it a default;
%macro nobs(library_name, table_name, toReturn=nobs);
Make sure the variable to return exists
If it exists it is known outside of this macro.
Otherwisse if we create it here, it wil by default be local and lost when we leave the macro;
%if not %symexist(&toReturn.) %then %global &toReturn.;
In the SQL, I
use the SASHELP.VTABLE, a view provided by SAS on its meta data
add the quotes I omitted in the macro call ("", not '': macro variables are not substituted in single qoutes)
use the macro %upcase function instead of the SAS upcase function, as it sometimes improves performance;
proc sql noprint;
select nobs
into :&toReturn.
from sashelp.vtable
where libname = %UPCASE("&library_name.")
and memname = %UPCASE("&table_name.");
quit;
%mend;
Pay attention if you call a macro within a macro, Run this code and read the log to understand why;
%macro test_nobs();
%nobs(sashelp, class); ** will return the results in nobs **;
%nobs(sashelp, shoes, toReturn=num_of_shoes);
%let num_of_cars = ;
%nobs(sashelp, cars, toReturn=num_of_cars);
%put NOTE: inside test_nobs, the following macro variables are known;
%put _user_;
%mend;
%test_nobs;
%put NOTE: outside test_nobs, the following macro variables are known;
%put _user_;
You can't 'return' a value from a function-style macro unless you have written it using only macro statements. Quentin's link provides an example of how to do this.
For example, you cannot use your macro like so, because proc sql cannot execute in the middle of a %put statement (this is possible with other more complex workarounds, e.g. dosubl, but not the way you've written it).
%put %nobs(mylib,mydata);
The best you can do without significant changes is to create a global macro variable and use that in subsequent statements.
To create a macro variable that is local to the originating macro, you have to first declare it via a %local statement within the macro definition.
I know I am very late to this discussion, but thought of commenting since I came across this. This is another way of doing this I think:
%macro get_something_back(input1, input2, output);
&output = &input1 + &input2;
%mend;
data _test_;
invar1 = 1; invar2 = 2;
%get_something_back(invar1, invar2, outvar);
end;
This will also work outside a datastep.
%global sum;
%macro get_something_back(input1, input2, outvar);
%let &outvar = &sysevalf(&input1 + &input2);
%mend;
%get_something(1, 2, sum);

What is the importance of the % symbol as used inside a SAS macro

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