Using %IF-%THEN-%ELSE in SAS programs

SAS programmers have long wanted the ability to control the flow of their SAS programs without having to resort to complex SAS macro programming. With SAS 9.4 Maintenance 5, it's now supported! You can now use %IF-%THEN-%ELSE constructs in open code. This is big news -- even if it only recently came to light on SAS Support Communities. (Thanks to Super User Tom for asking about it.)

Prior to this change, if you wanted to check a condition -- say, whether a data set exists -- before running a PROC, you had to code it within a macro routine. It would look something like this:

/* capture conditional logic in macro */ %macro SummarizeIfExists(); %if %sysfunc(exist(work.result)) %then %do; proc means data=work.result; run; %end; %else %do; %PUT WARNING: Missing WORK.RESULT - report process skipped.; %end; %mend; /* call the macro */ %SummarizeIfExists();

Now you can simplify this code to remove the %MACRO/%MEND wrapper and the macro call:

/* If a file exists, take an action */ /* else fail gracefully */ %if %sysfunc(exist(work.result)) %then %do; proc means data=work.result; run; %end; %else %do; %PUT WARNING: Missing WORK.RESULT - report process skipped.; %end;

Here are some additional ideas for how to use this feature. I'm sure you'll be able to think of many more!

Run "debug-level" code only when in debug mode

When developing your code, it's now easier to leave debugging statements in and turn them on with a simple flag.

/* Conditionally produce debugging information */ %let _DEBUG = 0; /* set to 1 for debugging */ %if &_DEBUG. %then %do; proc print data=sashelp.class(obs=10); run; %end;

If you have code that's under construction and should never be run while you work on other parts of your program, you can now "IF 0" out the entire block. As a longtime C and C++ programmer, this reminds me of the "#if 0 / #endif" preprocessor directives as an alternative for commenting out blocks of code. Glad to see this in SAS!

/* skip processing of blocks of code */ /* like #if 0 / #endif in C/C++ */ %if 0 %then %do; proc ToBeDetermined; READMYMIND = Yes; run; %end;

Run code only on a certain day of the week

I have batch jobs that run daily, but that send e-mail to people only one day per week. Now this is easier to express inline with conditional logic.

/*If it's Monday, send a weekly report by email */ %if %sysfunc(today(),weekday1.)=2 %then %do; options emailsys=smtp emailhost=myhost.company.com; filename output email subject = "Weekly report for &SYSDATE." from = "SAS Dummy " to = "knowledgethirster@curious.net" ct ='text/html'; ods tagsets.msoffice2k(id=email) file=OUTPUT(title="Important Report!") style=seaside; title "The Weekly Buzz"; proc print data=amazing.data; run; ods tagsets.msoffice2k(id=email) close; %end;

Check a system environment variable before running code

For batch jobs especially, system environment variables can be a rich source of information about the conditions under which your code is running. You can glean user ID information, path settings, network settings, and so much more. If your SAS program needs to pick up cues from the running environment, this is a useful method to accomplish that.

/* Check for system environment vars before running code */ %if %sysfunc(sysexist(ORACLE_HOME)) %then %do; %put NOTE: ORACLE client is installed.; /* assign an Oracle library */ libname ora oracle path=corp schema=alldata authdomain=oracle; %end;

Limitations of %IF/%THEN in open code

As awesome as this feature is, there are a few rules that apply to the use of the construct in open code. These are different from what's allowed within a %MACRO wrapper.

First rule: your %IF/%THEN must be followed by a %DO/%END block for the statements that you want to conditionally execute. The same is true for any statements that follow the optional %ELSE branch of the condition.

And second: no nesting of multiple %IF/%THEN constructs in open code. If you need that flexibility, you can do that within a %MACRO wrapper instead.

And remember, this works only in SAS 9.4 Maintenance 5 and later. That includes the most recent release of SAS University Edition, so if you don't have the latest SAS release in your workplace, this gives you a way to kick the tires on this feature if you can't wait to try it.

About Author

Chris Hemedinger

+Chris Hemedinger is the Director of SAS User Engagement, which includes our SAS Communities and SAS User Groups. Since 1993, Chris has worked for SAS as an author, a software developer, an R&D manager and a consultant. Inexplicably, Chris is still coasting on the limited fame he earned as an author of SAS For Dummies.

32 Comments

Thank you Chris for spreading the word! I am looking forward to having iterative %DO in open code, as well as nested %IF-%THEN/%ELSE.

Chris Hemedinger

Chris Hemedinger on July 6, 2018 7:49 am

I'm not sure what the roadmap is for %DO and nested %IF/%THEN/%ELSE -- but I'll try to be satisfied with this new feature for now!

Don Henderson on July 6, 2018 8:31 am

Very nice. I've always been a fan of leaving debugging code in my programs. Even used _debug as the macro variable name to control that. This addition to M5 makes it orders of magnitude easier. Looking forward to anything you can report on the future roadmap.

Michelle Buchecker on July 6, 2018 5:11 pm

%sysfunc(today(),weekday1.)=2 any reason for this instead of the more direct &sysday=Monday ? Cool to know about that %IF though.

Chris Hemedinger

Chris Hemedinger on July 6, 2018 6:13 pm Um, internationalization? My code will work in Spain, as is?

In addition, &sysday resolves to the day the SAS session began execution which may not be the current day. Using %systunc(today(). . . . ensures you are getter by the day from the current time.

Chris Hemedinger

Chris Hemedinger on July 7, 2018 4:49 pm Yeah! What Don said!

Matthew on July 6, 2018 5:29 pm

Interesting! Chris, does code executed this way show up in the log as normal source code or do you need OPTIONS MPRINT to see it?

Chris Hemedinger

Chris Hemedinger on July 7, 2018 10:37 am

Good question Matthew! From my tests, all of the source code shows in the log even without MPRINT or MLOGIC. But of course, only the code that actually executes would produce additional log output.

Prashant Chegoor on July 7, 2018 1:17 pm

Also, the MLOGIC Option does NOT show any execution trace information in the SAS log for the %IF %THEN statement when used in Open Code. The trace Information is only displayed when the statement is contained within a Macro definition.

Scott Bass on July 9, 2018 12:07 am

Look, this is cool, and a welcome addition to SAS 9.4M5. But am I the only one who feels like this is around 20 years late? Are we so excited about this simply because the SAS base language has lacked modern programming constructs for so long? What I think would be even cooler is something like (it's just an illustration, to convey a concept - don't take it literally as the final syntax):

function foo(a,b); return a**b; endfunc; * or whatever. <> would also work, but would not be "sas-onic" ("pythonic") ; data foo; do x=1 to 10; do y=1 to 5; z=foo(x,y); /* not proc fcmp. a "proper" support of open code function definitions */ end end; run; if foo.nobs = 0 then do; print "foo is empty"; end; do name="class","stocks","shoes"; data.output=catx(".","work",name); set.input=catx(".","sashelp",name); run; end;

Instead, we still have a "C preprocessor" (https://en.wikipedia.org/wiki/C_preprocessor) (ok, a C preprocessor on steroids) to control what gets sent to the SAS compiler, instead of more modern programming constructs like ***open code*** functions, if statements, do & while loops, properties and methods, etc. The SAS data step language is great for data processing - I know it and use it every day. But it feels "tired" to me, compared to when I use Python, Java, C#, Powershell, and other modern programming languages. I know SAS is married to the idea that 40 year old code still runs, but I wish there was something like:

options saslang=newsas; fancy new modern SAS language; options saslang=oldsas; 40 year old SAS language;

Or perhaps just two different installs ("old SAS", "new SAS"), if the above would bloat SAS too much to have both code bases in the same application. (I hope I don't get flamed too badly for sharing these thoughts ;-) )

Chris Hemedinger

Chris Hemedinger on July 9, 2018 7:25 am

No flames from me. The SAS macro language has been the "control" language of SAS for decades, and those proficient in SAS macro have accomplished amazing feats of reuse and control. Of course it predates many of the other languages you mention, so the syntax has a learning curve. Also, projects like PROC LUA and the saskernel (Jupyter) and SASPy are designed to allow you to mix your "other favorite" languages with SAS. As far as new innovations and "new SAS", you'll find a ton of new syntax in SAS Viya/CAS. In addition to some modern constructs (action sets), the language is optimized for parallel processing and in-memory computation. See this paper for an introduction.

Try Proc DS2

DerylHollick on February 10, 2022 7:39 pm

I'm really late to this conversation, but anyone using SAS Viya now has the option to incorporate Python within their SAS programs:

%let inlib=sashelp; %let outlib=work; proc python; submit; import pandas as pd def foo(a,b): return a**b l=[] for x in range(1,11): for y in range(1,6): l.append() foo = pd.DataFrame(l) if foo.empty: print("foo is empty") else: ds = SAS.df2sd(foo,'work.foo') # write DataFrame to data set inlib = SAS.symget('inlib') outlib = SAS.symget('outlib') for name in ['class','stocks','shoes']: SAS.submit(f''' data .; set .; run; ''') endsubmit; run;


This is not a true "open code" solution, of course. The Python code has to be within the PROC boundaries, and the SAS code submitted from Python is a string. But, the Python session persists across multiple calls to proc python, so any functions and DataFrames are still available, and there are callbacks for macro variables and transfers to/from data sets and DataFrames. So it's a hybrid approach, but it gives the option to use whichever language components are best suited to the task at hand.

DerylHollick on February 10, 2022 8:46 pm The closest thing to a "fancy new SAS language" would be CASL:
proc cas; function foo(a,b); return(a**b); end; foo=newtable("foo", , ); do x = 1 to 10; do y = 1 to 5; z = foo(x,y); row = ; addrow(foo, row); end; end; saveresult foo casout="foo"; simple.numRows result=r / table="foo"; if r.numrows = 0 then do; print "foo is empty"; end; do name over ; datastep.runCode / code = "data "||catx(".","casuser",name)||"; set "||catx(".","public",name)||"; run;"; end;


quit; I wrote this using a SAS client, so I still have PROC boundaries, and the submitted DATA step is still a string. It's similar to the above example but allows for a mix of "old" and "new" SAS language components.

Suryakiran Pothuraju on July 10, 2018 6:17 pm

This is really a good news. Most recently I used macro routine with %IF-%THEN %ELSE to check if a macro variable exists or not. If exists I need to run one program and if not then other program. This new feature will simplify my code and also what I noticed in SAS EG is the color for the syntax will be maintained in open code, where as in between %MACRO - %MEND will be plain. %macro check();
%if %symexist(name) %then %do;
/* Proc sql code here is plain */
proc sql;
select * from sashelp.class
where name in (&name);
quit;
%end;
%mend check;
%check; %if %symexist(name) %then %do;
/* Proc sql code here maintains syntax color */
proc sql;
select * from sashelp.class
where name in (&name);
quit;
%end;

Jim Barbour on August 17, 2018 1:48 am

Suryakiran, You can retain colorization in macros with a simple trick:
%LET Null = ;
&Null %Macro Amazing_Macro;
. code goes here.
&Null %Mend Amazing_Macro; Best regards, Jim

Peter Lancashire on July 11, 2018 7:11 am

Thanks for this. It is never too late. My long-time partial workaround has been a %do_if() macro which evaluates the parameter and returns * if false, nothing if true. Example:

. %do_if(&DEBUG) %include "debug-code.sas"; .

Question: does this syntax work:
. %if &MYCONDITION %then %do; . some code . %end;

Chris Hemedinger

Chris Hemedinger on July 11, 2018 7:31 am

Peter, Yes, that works as long as &MYCONDITION is defined as a numeric value. 0 is "false", any other number is "true". If &MYCONDITION is undefined as a macro symbol, or is assigned as any other type (character string), I think you'll see an error in the log. And if you try something tricky like:

%if %symexist(mycondition) AND &MYCONDITION %then %do; %put Running this!; %end;


. you'll still see an error if &MYCONDITION is not defined. Looks like SAS will try (and fail) to resolve the macro value even if you attempt to short-circuit the check with a more complex boolean. So if you absolutely need this check, you would need to guarantee a defined value for the macro var.

/* in case some other process did not yet define this macro var */ %if %symexist(mycondition)=0 %then %do; %let mycondition = 0; %end; %if &MYCONDITION %then %do; %put Running this!; %end;

Karl Schaefer on July 26, 2018 10:16 am

Another trick I sometimes use is to just add a number within the condition itself:
%if 0&mycondition %then. I will always at least have a 0 to evaluate even if symbol mycondition is not defined.