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

32

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 <sasdummy@sas.com>"
      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.

WANT MORE GREAT INSIGHTS MONTHLY? | SUBSCRIBE TO THE SAS TECH REPORT
Share

About Author

Chris Hemedinger

Director, SAS User Engagement

+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

    • Chris Hemedinger
      Chris Hemedinger on

      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!

  1. Don Henderson on

    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.

  2. Michelle Buchecker on

    %sysfunc(today(),weekday1.)=2 any reason for this instead of the more direct &sysday=Monday ?

    Cool to know about that %IF though.

      • 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.

  3. 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

      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

      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.

  4. 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

      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.

    • 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({'x':x,'y':y,'z':foo(x,y)})
      
      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 {outlib}.{name};    
                                set {inlib}.{name};   
                             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.

    • 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", {'x','y','z'}, {'int64','int64','int64'});
      do x = 1 to 10;
         do y = 1 to 5;
            z = foo(x,y);
            row = {x, y, z};
            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 {'class','stocks','shoes'};
         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.

  5. Suryakiran Pothuraju on

    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;

    • 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

  6. Peter Lancashire on

    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

      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

        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.

    • Chris Hemedinger
      Chris Hemedinger on

      Hi Ted, SAS 9.4 Maintenance 5 has been available since last year (Sep 2017).

  7. I 100% agree with you, Scott. The fact that the SAS macro has accomplished amazing feats is not a justification for requiring everyone to use it. Being able to do if/then in open code seems like a requirement twenty years ago and not a great, new feature. Aside from the fact that there's no telling when my organization will get this, not being able to use nested if statements means this is only a very small improvement. As you suggested, why not have an option that reads open code and macros as text (i.e. - as SAS currently is) or allows you interact with SAS like a true programming language? Syntax, user interface, and functionality are all reasons why SAS has been such a frustrating part of my job.

    • Chris Hemedinger
      Chris Hemedinger on

      Mark, I can appreciate your view on this. My view: SAS isn't (just) a programming language -- it's a set of capabilities that reach deeper and broader than anything else I've seen. Having built systems that use a combination of SAS and something else (.NET, Java, HTML), I know that there are other languages that provide a better feel for certain types of applications. Still, when it comes to anything related to data, analytics, and reporting -- I would not want to be without SAS.

  8. And I think your comment gets to the root of my frustration. SAS is great to have for some of the specific tasks it's built to handle, but I don't see it as a programming language, at all. In cases like mine where all you have is SAS, trying to incorporate very basic programming concepts like if/then loops is very cumbersome which means hard-to-read code and lots of mistakes.

    I think it's also worth pointing out that SAS trails other options for the biggest data task of all - cleaning and understanding new data sets. Since so much of SAS is based on either point/click or printing out long reports, it takes far longer than it should to even get column names, convert between data types, etc. Here, the issue is a combination of both the 'language' and the IDE options you have for SAS. I can't print to a console, and I can't modify my workspace the way you can with lots of other IDEs.

  9. Pingback: Conditionally append observations to a SAS data set - The DO Loop

  10. Hello,

    Please help, new in SAS Macros.

    Trying to do something like this, please correct me.

    OPtions Symbolgen Mprint Mlogic;
    proc sql;
    select MAX(MO_END) format 10. into: MCDMAX /* eg- Date to Num format- 31Aug2019-21792*/
    from MHH_view;quit;

    proc sql;
    select MAX(MO_END) format 10. into: IDPMAX /* eg- Date to Num format-30Sep2019-21822*/
    from IDPHH_view;quit;
    %put IDPMAX=&IDPMAX.;
    %put MCDMAX=&MCDMAX.;
    /* Check if XZY is updated with latest month data then pick the latest month data and append to ABC */
    %If (&mcdmx.=&idpmx.) %THEN %Do;
    proc append
    base=ABC
    data=XYZ
    run;
    %End;

    I am getting error - ERROR: The %IF statement is not valid in open code.

  11. Pingback: Gifts to give the SAS fan in your life - SAS Users

  12. Greetings all!!

    How would one syntax the code to make it perform the DO if the table is NOT there? As in if the table doesn't exist, create it?

    "%if %sysfunc(exist(work.result)) %then" <---- where in this line would I slip in the "Not"?

    As it is, for the time being, I can put the code I want to run in the Else Do and it runs, but I'm quite sure it doesn't need to be that ponderous!! =o)

    Thanks in Advance!!

    • Chris Hemedinger
      Chris Hemedinger on

      Since the EXIST function returns a 0 if the member does not exist, you can check for that in the condition:

      %if %sysfunc(exist(work.class)) = 0 %then %do;
       data class; set sashelp.class; run;
      %end;
      %else %do;
       %PUT Note: Already exists!;
      %end;

Back to Top