In data analysis, sometimes we need to perform a preliminary task before we can analyze data. Often the task needs to be performed only once per session. For example, you might need to download or merge data prior to your analysis. Or you might need to define or load a set of helper functions. These might be user-defined functions in PROC FCMP or a library of SAS IML modules.
I often place the SAS code that performs the preliminary task in a file. I use an %INCLUDE statement at the top of my program to read the file. This method is convenient, but the file is re-read and the preliminary task is re-run every time I resubmit the program. Sometimes I comment out the %INCLUDE statement so that the preliminary task does not run for future submissions.
If you want to be more efficient, you can use macro logic to execute a preliminary task only once per session. This article shows how to wrap the task in a SAS macro and use a macro flag variable to remember that the task has been run.
Designing a one-time macro
A simple SAS macro that executes a preliminary task one time might look like this:
- Define a SAS macro function called Do_It_Once. The macro function contains the following logic:
- Check whether a macro symbol called Run_Task_Flag exists.
- If not, define the symbol and set its value to 1 (TRUE).
- If the value of Run_Task_Flag is 1:
- Run the SAS code that runs the one-time task.
- Set the value of Run_Task_Flag to 0 (FALSE).
- If the value of Run_Task_Flag is 0, do nothing.
Suppose you put the %Do_It_Once macro call in your program. The first time you run the program, the %Do_It_Once macro executes the one-time task. After the task completes, the Run_Task_Flag macro variable is set to 0. This flag value indicates that the preliminary task has been completed. On every subsequent call to the %Do_It_Once macro function, the flag is false, so the task does not execute.
A SAS macro that executes one time
To demonstrate the idea of a one-time macro, consider the following SAS macro definition. Instead of performing an actual preliminary task, the function merely uses a %PUT statement to show where the task is executed.
/* this macro contains all code that needs to be executed once */ %macro One_Time_Task; %put NOTE: The one-time task is being run; /* PUT THE REAL WORK HERE */ %mend; %macro Do_It_Once; /* if the macro symbol Run_Task_Flag does not exist, create it and set it to 1 */ %if %symexist(Run_Task_Flag) = 0 %then %do; %global Run_Task_Flag; %let Run_Task_Flag = 1; %end; /* if Run_Task_Flag=1, run the %One_Time_Task code */ %if &Run_Task_Flag eq 1 %then %do; %One_Time_Task; %let Run_Task_Flag = 0; %end; /* otherwise, do nothing. */ %else %do; %put NOTE: The macro call does nothing.; %end; %mend Do_It_Once; |
Let's call the macro twice to see how the behavior changes. This example uses the %SYMDEL statement to ensure that the Run_Task_Flag symbol is undefined prior to calling the macro the first time.
%symdel Run_Task_Flag; /* For testing, make sure symbol is undefined */ %Do_It_Once; /* First call. This will run the task. */ %Do_It_Once; /* Second call. This will do nothing. */ |
The SAS log shows the following text:
24 %Do_It_Once; /* First call. This will run the task. */ NOTE: The one-time task is being run 26 %Do_It_Once; /* Second call. This will do nothing. */ NOTE: The macro call does nothing. |
Forcing the task to execute
There are times when you want to re-run the preliminary task. For example, if I am developing a set of helper functions, I might find a bug during testing. After fixing the bug, I want to re-run the complete set of definitions so that I can re-run my test suite.
There are three ways to force the macro in the previous section to re-run the one-time task:
- Delete the flag variable by running %SYMDEL Run_Task_Flag.
- Reset the flag variable by running %LET Run_Task_Flag=1.
- Modify the macro so that it includes an input argument that forces the task to execute.
The first two options require internal knowledge of the %Do_It_Once macro. This might be fine for your personal programs, but the third option is better if you intend to share your code with others.
The following macro definition adds an optional argument called FORCE to the macro. If you call the macro as Do_It_Once(Force=1), then the preliminary task will be re-run:
%macro Do_It_Once(Force=); /* if FORCE=1 or Run_Task_Flag does not exist, set Run_Task_Flag=1 */ %if (%length(&Force) > 0) or (%symexist(Run_Task_Flag) = 0) %then %do; %global Run_Task_Flag; %let Run_Task_Flag = 1; %end; /* if Run_Task_Flag=1, run the %One_Time_Task code */ %if &Run_Task_Flag eq 1 %then %do; %One_Time_Task; %let Run_Task_Flag = 0; %end; /* otherwise, do nothing. */ %else %do; %put NOTE: The macro call does nothing.; %end; %mend Do_It_Once; /* test the macro in several ways */ %symdel Run_Task_Flag; /* For testing, make sure symbol is undefined */ %Do_It_Once; /* First call. This will run the task. */ %Do_It_Once; /* Second call. This will do nothing. */ %Do_It_Once(Force=1); /* Force the task to run. */ |
The SAS log shows that calling the macro with FORCE=1 results in executing the task:
184 %Do_It_Once; /* First call. This will run the task. */ NOTE: The one-time task is being run 185 %Do_It_Once; /* Second call. This will do nothing. */ NOTE: The macro call does nothing. 186 %Do_It_Once(Force=1); /* Force the task to run. */ NOTE: The one-time task is being run |
Summary
This article shows how to use simple macro logic to run a SAS task once and only once. By placing a task inside the %Do_It_Once macro, you can resubmit a program multiple times, but the task runs only once. You can override this behavior by calling the macro with FORCE=1. This technique is useful for preliminary tasks such as preparing data for analysis or defining a library of functions in PROC FCMP or PROC IML.
Have you ever used this idea? Can you think of a better way to implement it? Leave a comment!