Any automated program should check for errors and unexpected conditions, such as inability to access a resource and presence of invalid values. Unlike traditional programming languages such as Python and C# that stop processing when an error occurs, SAS barrels ahead through the rest of the program. Therefore, carelessly-written SAS programs can create unwanted side effects, such as overwriting an output data set with bad data.
Previously I wrote about a robust solution for checking SAS error codes which wraps the entire program in a macro and invokes %GOTO EXIT in case an error. This is still the ideal solution when some part of the program must continue, but it comes at a cost: wrapping SAS code in a macro disables syntax highlighting in the SAS Enhanced Editor (though not in SAS Studio). Also, it can be awkward to work with the large code block delimited by the macro, so this post focuses on two alternatives.
The easiest method is to enable OPTIONS ERRORABEND, which exits the program in case of an error. The major benefit is it requires only a single line of code to enable for the rest of the program, regardless of how many steps there are. For example:
OPTIONS ERRORABEND; /* Misspelled data set */ data female; set sashelp.clss; if sex='F'; run;
However, ERRORABEND can be painful while developing a program in interactive mode because it immediately exits the SAS session, and it cannot push error notifications.
This motivates the following approach. After each DATA STEP or procedure, call a macro that checks the sanity of preceding steps. In case of an error, the universal error handler can send a notification email, clean up, etc. Like ERRORABEND, syntax highlighting still works. Like the other options, it can check errors for DATA STEPs and various procedures. Though it requires code after each step, it is less code than the first solution proposed in the original article.
/* This macro is called from two other macros below. It defines any common error handling code, and it should be customized. In some programs, you may want to clean up here. */ %macro email_and_abort; /* Send an email */ filename mymail email "andrew@example.com" subject="error in SAS program"; data _null_; file mymail; put 'Check the SAS logs'; run; /* Stop further processing */ %abort cancel; %mend; /* This macro asserts the last procedure did not throw an error or a warning. We will invoke this macro after each step. */ %macro expect_no_syserr; /* If there is an error or warning... */ %if &syserr ne 0 %then %do; /* Write the error code to the SAS log */ %put ERROR: &=syserr;/ %email_and_abort; %end; %mend; /* Assuming SASHELP.CLASS exists and contains the character variable SEX, this DATA STEP will succeed. */ data female; set sashelp.class; if sex='F'; run; %expect_no_syserr; /* Likewise, the macro works with procedures such as PROC FREQ. */ proc freq data=sashelp.class noprint; table age/out=age_freq; run; %expect_no_syserr; /* This macro asserts PROC SQL creates at least one observation. */ %macro expect_any_obs; %if &sqlobs eq 0 %then %do; %put ERROR: no observations; %email_and_abort; %end; %mend; /* This PROC SQL shows that the assertions working with PROC SQL and that two assertions can be combined. */ proc sql; create table age_count as select age, count(1) as count from sashelp.class group by age; quit; %expect_no_syserr; %expect_any_obs; /* Misspelled data set. This will fail to demonstrate the macros. */ data female; set sashelp.clss; if sex='F'; run; %expect_no_syserr;
Similar assertion macros can check that a data set exists, variables exist in a a data set, observations exist in a data set, a path is writable, a file exists, and so on.
Tested with SAS 9.4M3.
In the macro call: %macro expect_no_syserr; you call %email_and_stop;
ReplyDeletebut I believe you meant %email_and_abort; as it errors out
William, yes! You found an error in the error handling :)
ReplyDeleteThis post is exactly what I was looking for. Thanks so much for sharing :)
ReplyDelete