BookmarkSubscribeRSS Feed

SAS Macros: Error Handling Best Practices

Started 3 weeks ago by
Modified 3 weeks ago by
Views 532

If you’ve worked with SAS macros, you’ve probably inherited one written by someone else, and had no idea how to use it. Or maybe you’re the macro writer, looking for ways to handle unexpected parameters and make your macro easier for others to use. Good macro error handling isn’t about catching every possible failure. It’s about making failures visible, early, and diagnosable, so you or whoever inherits your code isn’t debugging blind six months from now.

 

In this post, we’ll explore three practical techniques for making your macros more robust:

 

  • Normalize user input so differences in casing don’t lead to silent failures.
  • Build in self-documentation so users can discover how to use the macro without digging through the source code.
  • Validate parameters so invalid values produce clear, actionable error messages.

 

To make these techniques concrete, we’ll work through a real example using the SASHELP.CARS table. You can experiment with these techniques and apply them to your own SAS macros.

 

 

Starter Macro

 

Throughout this post, we'll enhance the macro below to make it more robust. The macro uses SASHELP.CARS to create a bar chart showing the number of cars by type. When calling the macro, the user inputs one parameter to filter the chart for the origin. Currently a report is generated if the parameter value is Asia, Europe, USA, or All.

 

What happens if a user inputs a value with incorrect casing, or an origin that does not exist in the dataset, or if they don’t even know the parameter value is supposed to be an origin? Disaster strikes!

 

Together, let’s add on to this macro to make it more robust, and ready for many users.

 

/* Define macro */

%macro origin_chart(origin_filter);

    %let origin_check = &origin_filter;

    /* Print chart for all origins */
    %if &origin_check = All %then %do;
        title "Number of Cars by Type for All Origins (Ordered by Descending Frequency)";
        proc sgplot data=sashelp.cars;
            vbar Type /
                stat=freq
                categoryorder=respdesc
                group=Type
                groupdisplay=cluster
                datalabel
                nooutline;
        run;
        title;
    %end;

/* Print chart for specific origin */

    %else %do;

        /* Title formatting logic */    
        %if &origin_check = USA %then %do;
            %let origin_display = %upcase(&origin_filter);
        %end;
        %else %do;
            %let origin_display = %sysfunc(propcase(&origin_filter));
        %end;

        title "Number of Cars by Type for &origin_display (Ordered by Descending Frequency)";
        proc sgplot data=sashelp.cars(where=(Origin="&origin_check"));
            vbar Type /
                stat=freq
                categoryorder=respdesc
                group=Type
                groupdisplay=cluster
                datalabel
                nooutline;
        run;
        title;
    %end;

%mend origin_chart;

/* Sample macro calls */

%origin_chart(All)
%origin_chart(Asia)
%origin_chart(Europe)
%origin_chart(USA)
 
Sample report:

 

cjc_Picture5.png

Select any image to see a larger version.
Mobile users: To view the images, select the "Full" version at the bottom of the page.

 

 

Step 1: Eliminate Case Sensitivity

 

The first step in enhancing our macro is to handle various casings of the parameter value. Ensuring that a user can enter a parameter value in any case significantly reduces errors or unexpected results. Currently, if a user called the macro with a value of usa (lowercase), no report is generated and the log prints the note:

 

cjc_Picture2.png

This happened because WHERE filters in SAS are case sensitive, and none of the values in Origin match lowercase usa. We can use the %UPCASE macro function to uppercase resolved parameter values in our macro. Additionally, we can compare the resolved value to an all uppercase value.

I'll make the following changes to the code:
 
%if %upcase(&origin_filter) = ALL %then %do;

 

The %UPCASE macro function ensures any casing of the parameter value all is uppercase and prints a report for all origins.

 

%else %do;

        /* Standardize for validation */
        %let origin_check = %upcase(&origin_filter);

        /* Validate Origin value */
        %if &origin_check = USA or
            &origin_check = ASIA or
            &origin_check = EUROPE %then %do;

            /* Title formatting logic */
            %if &origin_check = USA %then %do;
                %let origin_display = %upcase(&origin_filter);
            %end;

            %else %do;
                %let origin_display = %sysfunc(propcase(&origin_filter));
            %end;

            title "Number of Cars by Type for &origin_display (Ordered by Descending Frequency)";
            proc sgplot data=sashelp.cars (where=(upcase(Origin)="&origin_check"));
                 ...

 

Adding %UPCASE to the %LET statement converts the user-provided value to uppercase, regardless of how the user entered it.

 

The &origin_check macro variable stores the uppercase origin value, which can then be compared to USA, ASIA, or EUROPE.

 

In the WHERE= dataset option, the UPCASE function converts the values in the Origin column of SASHELP.CARS to uppercase. These values are then compared to the uppercase value stored in &origin_check.

 

 

Step 2: Make the Macro Self-Documenting

 

Another best practice when making macros that you or others plan to reuse and share is to make the macro self-documenting. You may have a standard within your organization; however, this example shows allowing help, a question mark (?), or no value to trigger printing how to use the macro to the log.

 

To do this we can add an additional %IF %THEN %DO block at the beginning of the macro.

 

/* Self-documentation check: blank, HELP, or ? */

%if %length(%superq(origin_filter)) = 0 
    or %upcase(%superq(origin_filter)) = HELP 
    or %superq(origin_filter) = %str(?) %then %do;

        %put ------------------------------------------------------------;
        %put NOTE: %nrstr(%origin_chart) - Usage Information;
        %put ------------------------------------------------------------;
        %put NOTE: Generates a bar chart of car counts by Type from sashelp.cars,;
        %put NOTE: optionally filtered to a single Origin.;
        %put NOTE: ;
        %put NOTE: Parameter:;
        %put NOTE:   origin_filter=  ALL, USA, ASIA, or EUROPE (any case).;
        %put NOTE:                   Leave blank, or pass HELP or ? to see this note.;
        %put NOTE: ;
        %put NOTE: Example calls:;
        %put NOTE:   %nrstr(%origin_chart(all));
        %put NOTE:   %nrstr(%origin_chart(usa));
        %put NOTE:   %nrstr(%origin_chart(europe));
        %put ------------------------------------------------------------;
        %return;
%end;
 
The %LENGTH function checks if the parameter value is a length of 0, meaning no value was provided. The second condition checks if the parameter value was help in any case. The third condition checks if the parameter was a question mark.

 

Inside of the macro %DO loop, %PUT statements are used to write a description of the macro, how to use it, and sample macro calls.

 

  • %PUT NOTE: writes blue notes to the SAS log.
  • %PUT WARNING: writes yellow warnings to the SAS log.
  • %PUT ERROR: writes red errors to the SAS log.

 

In the example calls, the macro function %NRSTR is used around the macro call to ensure the macro triggers are seen as text and that the macro does not attempt to resolve and run.

 

The %RETURN statement terminates execution of the macro. Using it inside of this %DO loop ensures that the macro stops execution after the notes are printed to the log.

 

Now when help, a question mark, or no value is provided, the following prints to the log:

 

cjc_Picture3.png

 

Step 3: Expect the Unexpected

 

Finally, we need to expect the unexpected, meaning we can handle a user inputting an unexpected parameter value. To do this, add a final %ELSE %DO block to serve as a “catch all” and print how to use the macro to the log.

 

%else %do;
    %put NOTE: Valid values for origin_filter are Asia, Europe, or USA (any case).;
    %put NOTE: Call %nrstr(%origin_chart(help)) for full usage information.;
    %put NOTE: Chart was not generated.;
%end;
 
Now, when an invalid value is entered, such as Africa, the following is printed to the log:

 

cjc-Picture4.png

 

Wrapping Up

 

Good error handling doesn’t mean anticipating every possible way a macro could be misused. As shown above, it comes down to three consistent habits: normalize user input to account for casing issues, document the macro so anyone can discover how to call it without reading the code, and validate parameters so invalid values result in a clear, actionable message.

 

These same three habits apply to almost any macro you write or inherit. Building them in up front costs a little extra code, but it pays for itself the first time someone — including future you — calls the macro the wrong way and gets a clear answer in the log instead of a silent failure or a cryptic error.

 

 

The Final Program

 

The final program below contains all error-handling techniques. It's ready for handoff to other users and will significantly improve reusability. Happy coding!

 

/* Define macro */

%macro origin_chart(origin_filter);

    /* Self-documentation check: blank, HELP, or ? */

    %if %length(%superq(origin_filter)) = 0 
        or %upcase(%superq(origin_filter)) = HELP 
        or %superq(origin_filter) = %str(?) %then %do;

            %put ------------------------------------------------------------;
            %put NOTE: %nrstr(%origin_chart) - Usage Information;
            %put ------------------------------------------------------------;
            %put NOTE: Generates a bar chart of car counts by Type from sashelp.cars,;
            %put NOTE: optionally filtered to a single Origin.;
            %put NOTE: ;
            %put NOTE: Parameter:;
            %put NOTE:   origin_filter=  ALL, USA, ASIA, or EUROPE (any case).;
            %put NOTE:                   Leave blank, or pass HELP or ? to see this note.;
            %put NOTE: ;
            %put NOTE: Example calls:;
            %put NOTE:   %nrstr(%origin_chart(all));
            %put NOTE:   %nrstr(%origin_chart(usa));
            %put NOTE:   %nrstr(%origin_chart(europe));
            %put ------------------------------------------------------------;
            %return;
    %end;

    /* Print chart for all origins */

    %else %if %upcase(&origin_filter) = ALL %then %do;

        %put NOTE: No origin_filter specified. Chart generated for ALL origins.;

        title "Number of Cars by Type for All Origins (Ordered by Descending Frequency)";
        proc sgplot data=sashelp.cars;
            vbar Type /
                stat=freq
                categoryorder=respdesc
                group=Type
                groupdisplay=cluster
                datalabel
                nooutline;
        run;
        title;
    %end;

    /* Print chart for specific origin */

    %else %do;

        /* Standardize for validation */

        %let origin_check = %upcase(&origin_filter);

        /* Validate Origin value */

        %if &origin_check = USA or 
            &origin_check = ASIA or 
            &origin_check = EUROPE %then %do;

            /* Title formatting logic */

            %if &origin_check = USA %then %do;
                %let origin_display = %upcase(&origin_filter);
            %end;

            %else %do;
                %let origin_display = %sysfunc(propcase(&origin_filter));
            %end;

            title "Number of Cars by Type for &origin_display (Ordered by Descending Frequency)";
            proc sgplot data=sashelp.cars(
                           where=(upcase(Origin)="&origin_check"));
                vbar Type /
                    stat=freq
                    categoryorder=respdesc
                    group=Type
                    groupdisplay=cluster
                    datalabel
                    nooutline;
            run;
            title;
        %end;

        %else %do;

            %put NOTE: Valid values for origin_filter are Asia, Europe, or USA (any case).;
            %put NOTE: Call %nrstr(%origin_chart(help)) for full usage information.;
            %put NOTE: Chart was not generated.;
        %end;

    %end;

%mend origin_chart;

/* Sample macro calls */

%origin_chart(All)
%origin_chart(Asia)
%origin_chart(Europe)
%origin_chart(usa)
%origin_chart()
%origin_chart(help)
%origin_chart(?)
%origin_chart(africa)

 

 

Find more articles from SAS Global Enablement and Learning here.

Comments
Tom

Parameter validation is an important tool for making macros that are easy to use and also easy to create.  The late Tom Hoffman created an excellent utility macro for implementing this idea over 30 years ago named %PARMV.  You can find an updated version of the macro on github at https://github.com/sasutils/macros/blob/master/parmv.sas.

 

Using this macro will insure that your error messages are consistently styled and also make it easy to implement common parameter checking features such as automatic case conversion, valid value checking, default values, etc.  You can read the source code of the %PARMV() macro to see what else it can, including in the comment block some simple examples.

 

In your example you will want to use the _VAL= parameter of %PARMV() to pass in the list of valid values.  You will also want to use _DEF= parameter to pass in the fact that ALL should be the default value.  And leave the _CASE= parameter at its default setting (U for uppercase).

%parmv(origin_filter,_val=all usa asia europe,_def=all)

So using this macro your example program can be reduced to this much simpler definition.

%macro origin_chart
/*-----------------------------------------------------------------------
Generate a bar chart of car counts by Type from sashelp.cars
-----------------------------------------------------------------------*/
(origin /* optional filter to a single Origin value */
);

/*-----------------------------------------------------------------------
Generate a bar chart of car counts by Type from sashelp.cars

Example calls:;
%origin_chart(all)
%origin_chart(usa)
%origin_chart(europe)
-----------------------------------------------------------------------*/
%local parmerr ;
%parmv(origin,_val=all usa asia europe,_def=all)
%if (not &parmerr) %then %do;

title "Number of Cars by Type for &origin (Ordered by Descending Frequency)";            
proc sgplot data=sashelp.cars;
%if ALL ne &origin %then %do;
  where upcase(Origin)="&origin";
%end;
  vbar Type /
      stat=freq
      categoryorder=respdesc
      group=Type
      groupdisplay=cluster
      datalabel
      nooutline
  ;
run;
title;

%end;
%mend origin_chart;

Example log:

Screenshot 2026-09-09 at 9.45.26 PM.png

 

There is another key feature of parameter checking that you did not get to that %PARMV() can help with.  In a complex macro that might have multiple parameters the user might have provided more than one invalid parameter value.  It is extremely frustrating to have to fix your typos or mistakes one at a time.  By using the PARMERR flag variable %PARMV() will allow the coder of the macro to implement checks for all of the parameters and report all of the mistakes on the first call.  

 

 

Hi @Tom thank you so much for uncovering this hidden gem! That macro is fantastic. For the sake of keeping the example code "simple" I stuck to one parameter, however, knowing the %PARMV() macro also has checks for multiple parameters is even more helpful. Many thanks for commenting and including the example code! 

Good overview. Robust error handling is especially important in SAS macros because a macro can technically finish executing while producing incorrect or incomplete output. I find it useful to validate input datasets and parameters early, check &SYSERR and related automatic macro variables after critical procedures, and use clear %PUT messages so failures are easy to trace. Returning a clear status or stopping execution when a required condition fails can make reusable macros much safer in production.

Turn on the MPRINT option to make reading the log (and figuring out what the error is) much easier.

 

options mprint;

Excellent article, @CarleighJoC! I’ve loved SAS Macro ever since I discovered it way back in the 90s, and have been using the exact same self-documenting technique for a couple of decades now. I find it helpful because I can never remember the details of how to use a macro, even when I wrote it myself 😆
Will you be attending SEASUG this year? 

@AgricStatistics I absolutely agree! Great tip to check &SYSERR and automatic variables. %PUT messages are also great along with SYMBOLGEN and the MPRINT option suggested by @PaigeMiller . Thank you both for adding in your macro tricks!

Thanks so much @SASJedi - my passion for learning more about macros stems from you! You wouldn't believe the number of times I've looked something up and one of your blogs or solutions has come to my rescue. 

I won't be at SEASUG this year, but I'll be at BASUG in October (using this blog as a starting point for one of my presentations). 

We’re so excited you’re coming to BASUG to present in person at our annual SAS Blowout on Oct 30! We’re just putting the finishing touches on the official announcement. Registration opens Tues Sept 22. www.basug.org/events. 

 

Join us to see presentations by @CarleighJoC, Charu Shankar and Jane Eslinger. 

I can't wait for the event! @Quentin 

Contributors
Version history
Last update:
3 weeks ago
Updated by:

Viya Copilot Motion Graphic.gifViya Copilot Motion Graphic

Ready to see what SAS Viya Copilot can do?

Visit the Tips & Tricks page for setup guidance, demos, and practical examples that show how Copilot supports your workflows.

Get Started →

SAS AI and Machine Learning Courses

The rapid growth of AI technologies is driving an AI skills gap and demand for AI talent. Ready to grow your AI literacy? SAS offers free ways to get started for beginners, business leaders, and analytics professionals of all skill levels. Your future self will thank you.

Get started

Article Tags