SAS Macros

SAS supports a macro feature. For more options for extending and programming in SAS (or related software), see this crosswalk of programming features.


Macro Variables

Macro variables expand to text values that can be substituted into a program, in any location other than data input. They are identified by a label up to 32 characters, beginning with a letter or underscore and continuing with letters, numbers, and underscores.

Note that, similar to Unix shells, the macro processor will not substitute variables into single-quoted strings.

To delimit a macro variable label from immediately-appended text, use a period. That is, &prefix1 expects a macro under the name prefix1, while &prefix.1 expects a macro under the name prefix. (To 'escape' the period, use two.)

Built-in Macros

There are several built-in macro variables including %sysdate (system date as DATE7), or %systime (system time as TIME5).

Custom Macros

A macro variable can be defined as:

%let LABEL = VALUE;

These macros are referenced using &LABEL.

%let mon       = 1;
%let curr_yr   = %sysfunc(year("&sysdate"d));
%let mon_title = %sysfunc(mdy(&mon,1,&curr_yr), monname9.);
%let mon_title = %sysfunc(left(%qsysfunc(mdy(&mon,1,&curr_yr), monname9.)));

Note: %sysfunc cannot take nested functions, but can take nested %sysfunc calls.

Program-Set Macros

A macro variable can also be set by symputx calls. This enables programmatic creation of macros, for example to store aggregated values from a data step.

data _NULL_;
  set LIBREF.TABLE end=lastobs;
  retain sum_my_var 0;
  sum_my_var = sum_my_var + my_var;
  if lastobs then call symputx('sum_of_my_var', put(sum_my_var, dollar10.2));
run;
proc means data=LIBREF.TABLE;
  title "Allocation of &sum_of_my_var"
run;

Note: symputx and symput are very similar. The former is silent (i.e., does not log) and trims leading spaces when converting a value into character data (which all macros must be reduced into.

Scope

A macro variable defined by open code is stored in the global table. (And therefore: a macro variable defined in a separate program is local.) By the nature of SAS, most custom macros will be global.

If necessary, it is possible to force local resolution of a label.

%local A;
%let A=B;


Macro Programs

Built-in Macros

SAS comes with many functional macros for processing.

Given:

%let a=foo;
%let b=%nrstr(&a);
%let c=%nrstr(&a bar);

Name

Action

Example Input

Example Result

%nrstr(value)

Masks special characters inside value

%sysfunc(function call)

Evaluates a function call

%qsysfunc(function call)

Same, but masks function call

%upcase(value)

Translates lowercase into uppercase

%upcase(&a)

'FOO'

%upcase(&b)

'foo'

%qupcase(value)

Same, but masks value

%qupcase(&b)

'&A'

%lowcase(value)

Translates uppercase into lowercase

%qlowcase(value)

Same, but masks value

%cmpres(value)

Strips leading/trailing and multiple whitespace

%qcmpres(value)

Same, but masks value

%left(value)

Strips leading whitespace

%qleft(value)

Same, but masks value

%substr(value,position,length)

Copies a substring from a value

%substr(&c,4,3)

'bar'

%substr(&a,1,2)

'foo'

%qsubstr(value,position,length)

Same, but masks value

%qsubstr(&a,1,2)

'&a'

%eval(expression)

Evaluates expression using integer arithmetic

%eval(10/3)

'3'

%sysevalf(expression)

Evaluates expression using floating point arithmetic

%sysevalf(10/3)

'3.333333'

The difference between sysfunc and qsysfunc is very fine.

This results in an error:

%let date=%sysfunc(left(%sysfunc(today(),worddate.)));
/*                      ^
                        June 7, 2002
          ^
          left(June 7, 2002)
                     ^
                     Error: `left` takes only one argument
*/

This will not:

%let date=%sysfunc(left(%qsysfunc(today(),worddate.)));


Arguments and Options

A macro can take both positional arguments and keyword options. Keyword options can also take default values, or be left null.

%macro mymacro(start, stop, opts=, stats=N SUM MIN MAX);
  proc means data=LIBREF.TABLE &stats &opts;
    where "&start"d <= date <= "&stop"d;
  run;
%mend mymacro;

%mymacro(01JAN2020,01JAN2021)


Looping

It is possible to iterate over ranges using %do LABEL=START %to END. The loop value is accessed by &LABEL.

%macro mymacro;
  %do year=2000 %to 2020;
    proc means data=LIBREF.data_from_&year;
      title "Data from &year";
    run;
  %end;
%mend mymacro;

%mymacro


Conditional Processing

%macro mymacro(dsname);
  /* `open()` returns a positive integer identifying the opened dataset, or 0 on error */
  %let dsid = %sysfunc(open(&dsname));

  %if (&dsid > 0) %then %do
    %let nobs = %sysfunc(attrn(&dsid, nobs));
    %let rc = %sysfunc(close(&dsid));

    title "Report on &dsname";
    title2 "Number of Observations: &nobs";

    proc means data=&dsname;
      var VAR;
    run;
  %end;
%mend;

%mymacro(myds)


Dispatching Macros

The macro processor scans from right to left in each token. It looks up every valid label (i.e., an ampersand followed by a character or underscore) it encounters, and also substitutes double ampersands with single ampersands. However, it will re-scan until all ampersands have been cleared. Therefore, double ampersands can be used to 'defer' processing.

%let section1=Math;
%let section2=Science;
%let section3=Literature;

%let n = 2;
proc means data=LIBREF.TABLE;
  where section=&&section&n;
run;

The rabbit hole does not end here.

%let wherevar=section;
proc means data=LIBREF.TABLE;
  where &wherevar=&&&wherevar&n;
run;


Debugging Macros

Set the mcompilenote option to see compilation errors in macro programs logged.

options mcompilenote=all;
/* do something */
options mcompilenote=none;

Enable the symbolgen option to see macro variable substitutions logged as they are processed. Enable the mprint option to see macro program commands as they are submitted.

options symbolgen mprint;
/* do something */
options nosymbolgen nomprint;

The %put macro command can log arbitrary text.

%if (&dsid=0) %then %put Could not open file &dsname;


CategoryRicottone