How to conditionally execute SAS global statements

33

SAS global statementsSAS global statements define certain SAS objects and set values for system options. They take effect as soon as SAS compiles programming statements and persist during the entire SAS session or until another global statement overrides them. They can be used in open code, within macros, as well as within DATA and PROC steps. Here are the most used global statements:

  • FILENAME statement
  • LIBNAME statement
  • OPTIONS statement
  • TITLE statement
  • FOOTNOTE statement
  • X statement

NOTE: In SAS Viya, global statements are not supported on the CAS server.

See also: How to conditionally stop SAS code execution and gracefully terminate SAS session

Real meaning of “global statements are not executable”

I know, the title of this blog post may sound like an oxymoron. Even though SAS global statements can appear in DATA steps, they are not executable by definition. They are so not executable, that they even syntactically cannot be a part of the IF-THEN/ELSE statement that executes DATA step statements depending on specified conditions:

IF expression THEN executable-statement1;
<ELSE executable-statement2;>

Try sticking it in there and SAS will slap you with an ERROR:

data _null_;
   set SASHELP.CARS nobs=n;
   if n=0 then libname outlib 'c:\temp';
run;

SAS log will show:

3       if n=0 then libname outlib 'c:\temp';
                    -------
                    180
ERROR 180-322: Statement is not valid or it is used out of proper order.

But global statements’ “not executable” status only means that they cannot be executed as part of a DATA step execution. Otherwise, “they take effect” (in my mind that equates to “they execute”) right after the compilation phase but before DATA step executes (or processes) its data reads, writes, logic and iterations.

Here is another illustration. Let’s get a little creative and tack a LIBNAME global statement within conditionally executed DO-group of the IF-THEN statement:

data OUTLIB.CARS;
   set SASHELP.CARS nobs=n;
   if n=0 then
   do;
      libname OUTLIB 'c:\temp';
   end;
run;

In this case, SAS log will show:

NOTE: Libref OUTLIB was successfully assigned as follows:
      Engine:        V9
      Physical Name: c:\temp
NOTE: There were 428 observations read from the data set SASHELP.CARS.
NOTE: The data set OUTLIB.CARS has 428 observations and 15 variables.

As you can see, not only our LIBNAME statement “executed” (or “took effect”) despite the IF-THEN condition was FALSE, it successfully assigned the OUTLIB library and applied it to the data OUTLIB.CARS; statement that appears earlier in the code. That is because the LIBNAME global statement took effect (executed) right after the DATA step compilation before its execution.

For the same reason, you can place global statement TITLE either in open code before PROC that produces output with a title or within that PROC. In the first case, the stand-alone TITLE statement is compiled on its own and immediately executed thus setting the title for the PROCs that follow. In the latter case, it is compiled with the PROC step, then immediately executed before PROC step’s execution.

Now, when we have a solid grasp of the global statements timing habits, let’s look at the coding techniques allowing us to take full control of when and whether global statements take effect (executed).

Macro language to conditionally execute SAS global statements

Since SAS Macro processor acts before SAS language compiler, we can conditionally generate SAS global statements using SAS macro language. These macro-generated global statements are then injected into SAS code passed on to the SAS compiler. SAS compiler receives these conditionally generated global statements and executes them accordingly.

Here is a code example:

%let dsname = SASHELP.CARS;
/*%let dsname = SASHELP.CLASS;*/
 
%let name = %scan(&dsname,2);
 
%if (&name eq CARS) or (&name eq CLASS) %then
%do;
   options DLCREATEDIR;
   libname outlib "c:\temp\&name";
%end;
%else
%do;
   libname outlib "c:\temp";
%end;
 
data OUTLIB.&name;
   set &dsname;
run;

In this code, if name is either CARS or CLASS the following global statements will be generated and passed on to the SAS compiler:

   options DLCREATEDIR;
   libname outlib "c:\temp\&name";

This will create a directory c:\temp\&name (if it does not exist) and assign libref OUTLIB to that directory.

Otherwise, the following global statement will be generated and passed on to the SAS compiler:

   libname outlib "c:\temp";

The DATA step then creates data set OUTLIB.&name in the corresponding dynamically assigned library. Using this technique, you can conditionally generate global statements for SAS system options, librefs, filerefs, titles, footnotes, etc. SAS compiler will pick up those generated global statements and execute (activate, put in effect) them.

CALL EXECUTE to conditionally execute SAS global statements

Sometimes, it is necessary to conditionally execute global statements based on values contained in data, whether in raw data or SAS data sets. Such a data-driven approach can be easily implemented using CALL EXECUTE routine in a DATA step. CALL EXECUTE routine resolves its argument and issues the resolved value for execution after a step boundary.

For example, suppose we want to produce separate report listings for each car make in SASHELP.CARS data set. We want titles of these listings to individually identify each car make.

Here is how this can be done:

data _null_;
   set SASHELP.CARS;
   by MAKE;
   if first.MAKE then
   do;
      call execute('title "'||trim(MAKE)||' models";');
      call execute('proc print noobs data=SASHELP.CARS(where=(MAKE="'||trim(MAKE)||'"));');
      call execute('   var MAKE MODEL TYPE;');
      call execute('run;');
   end;
run;

In this code, for every block of unique MAKE values (identified by first.MAKE) we have CALL EXECUTE generating lines of SAS code and pushing them outside the DATA step boundary where they compile and execute. The code snippets for TITLE and WHERE clause are data-driven and generated dynamically. The SAS log will show a series of the generated statements:

NOTE: CALL EXECUTE generated line.
1   + title "Acura models";
2   + proc print noobs data=SASHELP.CARS(where=(MAKE="Acura"));
3   +    var MAKE MODEL TYPE;
4   + run;
 
5   + title "Audi models";
6   + proc print noobs data=SASHELP.CARS(where=(MAKE="Audi"));
7   +    var MAKE MODEL TYPE;
8   + run;

. . . and so forth.

In this implementation, global statement TITLE is prepared (“pre-cooked”) conditionally (if first.MAKE is TRUE) within the DATA step in a form of a character value. It’s still not a global statement until CALL EXECUTE pushes it out of the DATA step. There it becomes a global statement as part of SAS code stream. There it gets compiled and executed, setting a nice data-driven title for the PROC PRINT output (individually for each Make):

PROC PRINT outputs with dynamically generated titles

Additional resources

Your thoughts?

Have you found this blog post useful? Do you have any questions? Please feel free to ask and share your thoughts and feedback in the comments section below.

See also: How to conditionally stop SAS code execution and gracefully terminate SAS session

Share

About Author

Leonid Batkhan

Leonid Batkhan is a long-time SAS consultant and blogger. Currently, he is a Lead Applications Developer at F.N.B. Corporation. He holds a Ph.D. in Computer Science and Automatic Control Systems and has been a SAS user for more than 25 years. From 1995 to 2021 he worked as a Data Management and Business Intelligence consultant at SAS Institute. During his career, Leonid has successfully implemented dozens of SAS applications and projects in various industries. All posts by Leonid Batkhan >>>

33 Comments

  1. Just finished reading post and all comment. People can learn and make their conception clear just with reading comments. So resourceful and easily comprehensive to understand how sas works!!!

    • Leonid Batkhan

      Thank you, Zahir, for your feedback. Indeed, comments is the main reason I like blogging. While blog posts serve as seeds, the comments section is where people can exchange different points of view, ideas, opinions, implementations...

  2. Jack Hamilton on

    You can also use CALL EXECUTE inside a compute block in PROC REPORT. It can be used to write conditional code to be run after the PROC REPORT, but my most common use is to write debugging statements to the SAS log.

    • Leonid Batkhan

      Interesting, I have never used CALL EXECUTE within PROC REPORT. What are the benefits of such use? Could you please share a use case and a code sample? What do you mean by "to write debugging statements to the SAS log"?

      • Jack Hamilton on

        It's useful for debugging. You can see what all of the values are at all of the break points (the whole report, groups, variables) and the order in which proc report ran them.

        This example is verbose, but it illustrates several capabilities.

        %macro writevalues();
            call execute(cat('%put >     ',
                  '_BREAK=', substr(_break_, 1, 10),
                  'Sex=', sex, ' ',
                  'Name=', name, ' ',
                  'Age=', age, ' ',
                  'Agemean=', agemean,
                  ';')
                  );
        %mend writevalues;
         
        proc report data=sashelp.class nowindows missing;
            where name =: 'J';
            columns sex name age age=agemean;
            define sex      / order;
            define name     / order;
            define age      / display;
            define agemean  / mean noprint;
         
            compute before;
                call execute('%put Compute Before;');
                %writevalues();
            endcomp;
         
            compute after;
                call execute('%put Compute After;');
                %writevalues();
            endcomp;
         
            compute before sex;
                call execute('%put Compute Before Sex;');
                %writevalues();
            endcomp;
         
            compute after sex;
                call execute('%put  ;');
            endcomp;
         
            compute age;
                call execute('%put Compute Age;');
                %writevalues();
            endcomp;
        run;

        When call execute has a parameter that is entirely macro code, it executes immediately, which is why the output is shown in the log for proc report. Non-macro code is sent to the SAS parser after the procedure finishes.

        Yes, this is complicated code, but sometimes it's the only way I can figure out what proc report is doing.

  3. Jack Hamilton on

    Another way to conditionally generate code is to write it to a temporary file which you then %include. Following your example above:

        filename tempcode temp;
     
        data _null_;
           set SASHELP.CARS;
           by MAKE;
           if first.MAKE then                                                                                      
           do;
              file tempcode;
              put 'title "' make 'models";              ' 
                / 'proc print noobs data=sashelp.cars;  '
                / '   where make="' make '";            '
                / '   var make model type;              '
                / 'run;                                 '
                ;
           end;
        run;
     
        %include tempcode / source2;
        filename tempcode clear;

    Which method to use depends on what you want to do and what you're most comfortable with. With the included file method, you don't have to use the trim function and the concatenate operator, and the template code inside the data step looks more like the final generated code and so might be easier to follow. There are other times when CALL EXECUTE might be easier to write or to understand. Both methods are good to know.

    • Leonid Batkhan

      Thank you, Jack, for your feedback.

      You brought up a broader topic of conditional code generation. For this purpose, in general, the method that you describe (writing to a file and then %include) provides greater flexibility than CALL EXECUTE which I used as an example of conditional execution of SAS global statements:
      1) you can %include not just right after the data step where this code was generated (as CALL EXECUTE does), but in other downstream places of your SAS program.
      2) you can generate not just SAS code, but any language code, including OS scripts, which can then be executed from your SAS code using X-statement or CALL SYSTEM().

  4. Peter Lancashire on

    Thanks for the clarification. I like to think of many of these statements as declarations: telling the SAS system about something to use later. Other languages make the distinction syntactically clearer, or restrict it to data types and structures, similar to some statements within the DATA step. This is one of the challenges of learning SAS, as other replies suggest.

    One area where processing and declaration are hopelessly confused is ODS. Some ODS statements merely declare something. Others actually do something immediately: the most infuriating is ODS HTML5 page="mypage.html"; which creates a page even if the following procedure creates no output. I had to discover this by experiment.

    I and all potential users of SAS would welcome any improvements in the consistency and clarity of SAS.

    • Leonid Batkhan

      Thank you, Peter, for your input. I agree, this is a rather subtle area, and this post was my attempt to bring some clarify to it. First of all, declarative vs. executable distinction is only defined for DATA step statements. But even there such a distinction is somewhat blurry. For example, RETAIN is considered to be declarative statement, but it's also responsible for initializing variable values at the beginning of each new iteration of a DATA step. For me, a better distinction would be conditionally executable vs. conditionally not executable. Those statements that can be part of IF-THEN/ELSE statement are conditionally executable, all the rest are conditionally not executable.
      Global statements, when used inside DATA step behave as conditionally not executable. But they do execute at some point, namely right after their compilation, before DATA step starts its implicit iterations. Does it make sense?

      • Peter Lancashire on

        Thanks, it makes sense as far as it goes, but I don't use the DATA step much. I always write global statements outside PROCs and the DATA step to make it clear. Most of these take effect when the RUN statement executes, but some appear to have an effect before that, maybe where run-group processing is used, e.g. in PROC SQL and PROC GLM. Some others, such as ODS PAGE=, take effect immediately. I have been using SAS for a long time and it is still not clear to me.

        Some kind of consistent terminology and documentation of all this would help. For example, documentation for PROC SQL shows the QUIT statement but PROC GLM does not. Why not show RUN and QUIT always in the documentation for PROCs? Try searching for these and you find nothing really useful. It is all very frustrating. Label all statements with "immediate" or "next run" or both. For the DATA step use "declaration" or "executable" or both (e.g. for retain).

        • Leonid Batkhan

          I agree, in many instances documentation would benefit from better consistency of descriptions and code examples. And this is one of the reasons I wrote this (as well and many others) blog post - to clarify things, to cut through words to their real meaning. Hope, I have succeeded, at least in some cases 🙂 .
          As for ODS PAGE=, it is a global ODS statement and as such executes (takes effect) immediately, as soon as it compiles.

  5. You have a way of making complex concepts simple. Thank you! I had an epiphany when I read this.

    For example, you said that global statements’ “not executable” status only means that they cannot be executed as part of a DATA step execution. Otherwise, “they take effect” (in my mind that equates to “they execute”) right after the compilation phase but before DATA step executes (or processes) its data reads, writes, logic and iterations.

    You cut through all the word nonsense "take effect" and "execute". I would add to that the word "process" as well.

    On a separate note:
    I know that macro statements are preprocessed by the "Macro Facility" and that happens before the "SAS Code" is processed.

    However, it is the terminology to explain this that is confusing - even with the C language, that bothers me. Doesn't the "macro facility" in SAS have to have its own "compiler" to, for example, make sure that there is a "mend;" at the end of the code block before it begins "compiling" the SAS program? I have always been confused about the whole idea of a "pre-processor" (https://en.wikipedia.org/wiki/C_preprocessor).
    The most important point is to know that the macro code gets executed first?
    Maybe I have macro-phobia? 🙂

    • Leonid Batkhan

      Thank you, Lisa, for your great input. I would "cut through all the word nonsense" about macro preprocessing as follows. Code processing is done in 2 steps. Step 1: Macro processor parses the code to find all macro-language elements (in SAS they are identified by % and &) and resolves them producing the final SAS language source code. Step 2: SAS language compiler parses SAS source code piece by piece; it compiles and execute one piece, then move on to the next piece.

      I think macro processor is not called macro compiler for the reason that it does not produce machine-ready code, but produces SAS language source code (that needs to be compiled and executed.) Compiler definition: Computer software that translates (compiles) source code written in a high-level language (e.g., C++) into a set of machine-language instructions that can be understood by a digital computer's CPU.

      • Hi Leonid,
        You wrote: "macro processor is not called macro compiler for the reason that it does not produce machine-ready code"
        Got it! That pretty much answered everything for me! That makes sense. I had forgotten what the definition of a compiler is. Is that why we use the term
        pre-processor?
        What happens if there is a syntax error in a macro? It is as if it has been compiled, right?

        • Leonid Batkhan

          Both, compiler and macro processor, perform some sort of "translation". By definition, compiler "translates" from some programming language into machine-ready code. Pre-processor or macro processor "translates" from one language (macro language) into another language (SAS programming language). They both fall under translators - "A translator or programming language processor is a generic term that can refer to anything that converts code from one computer language into another." You can use term translator to refer to either macro processor or compiler.

      • That explains it! (the translation to machine language). I had forgotten about that very important point/distinction. It has been a loooooong time since my assembly language course Thanks!

  6. Tatiana Pshenichnikov on

    After working with SAS for more than two decades I always learn something new from your posts!
    Thank you!

  7. John Cherrie on

    Nice! I think 'Call Execute' functionality is underutilized! This is especially useful conditionally executing libname and filename statements.

    For the 'X' command specifically, I've always used %sysexec (open code) or call system (data step) to conditionally execute code. Since %sysexec doesn't require quotes, I particularly like %sysexec with certain operating system commands that use quotes.

      • Leonid, another great post and discussion in comments. Very helpful in better understanding of Global statements 'taking effect'.

        Your line on SAS slapping is SAS humor gold.

        'Try sticking it in there and SAS will slap you with an ERROR:'

        Deb

        • Leonid Batkhan

          Thank you, Deb, for your feedback. Interestingly how you notice lines that most people pass by. Here is a little secret: treat SAS as an animated character, a buddy, and these "SAS humor" lines will come out naturally 🙂

  8. Rick Wicklin

    I realize that your example of using CALL EXECUTE is to demonstrate the concept, but I feel compelled to point out that your example can also be accomplished by using the BY statement in PROC PRINT. No need for any macro code or CALL EXECUTE statements:

    options nobyline;
    title "#byval1 models";
    proc print data=SASHELP.CARS noobs;
       by MAKE;
       var MAKE MODEL TYPE;
    run;
    options byline;

    For more information about controlling titles in By groups, see "How to use the #BYVAR and #BYVAL keywords to customize graph titles in SAS."

    • Leonid Batkhan

      Thank you, Rick, for your comment and the alternative solution for dynamically modifying TITLE using #BYVAL. Indeed, my goal was to demonstrate how to conditionally execute ANY global statement, not just TITLE, and not just for BY in SAS PROCs.

  9. Jose Santiago on

    I enjoyed your blog post. You wrote it in a very easy to follow manner that made the solutions to this challenge appear obvious. I look forward to your next post.

Leave A Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Back to Top