SAS Macro language – это основной и, чуть ли не единственный, инструмент повторного использования кода в SAS. В данном уроке рассматриваются основные его возможности.
Простой пример макро кода:
title "The Date is &sysdate9- the Time is &systime"; proc print data=demo noobs; run;
Оператор title задает заголовок, который выводится в окне Output. Обратите внимание на строку, которая задана в этом операторе: в этой строке используются макро переменные &sysdate9 и &systime. Это встроенные в SAS макро переменные, они хранят значение даты и времени начала текущей сессии SAS (то есть ту дату и время, когда вы запустили SAS).
В результате выполнения программы в окне Output вы увидите:

Как видите, макро переменная &sysdate9 преобразовалась в строку 23SEP2016, а &systime в строку 22:15.
Пользователь может самостоятельно создавать макро переменные и присваивать им значения. Один из способов это сделать – воспользоваться макро оператором %LET. Заметьте, все макро переменные начинаются со знака ‘&’, все макро операторы со знака ‘%’.
Итак, создадим свою макро переменную и воспользуемся ею:
%let study = 13-1014; data dm; set dm_raw; length STUDYID $ 7; STUDYID = "&study"; run;
Если значение переменной STUDYID должно быть одно и то же во всех датасетах в пределах данного исследования, то хорошим приемом будет задать глобальную макро переменную "&study" и пользоваться ею во всех программах.
Теперь рассмотрим пример создания простого макроса.
%macro reldays(date, rel_date, rel_days); &rel_days = &date - &rel_date + (&date >= &rel_date); %mend reldays;
Создание макроса всегда начинается с макро оператора %macro и заканчивается макро оператором %mend. Данный код создает макрос reldays, в качестве параметров, принимает макро переменные date, rel_date, rel_days. Назначение макроса уже должно быть понятным: посчитать количество дней от «базовой» даты rel_date до даты date.
Применим макрос для датасета AE:

Будем иметь в виду, что все даты в датасете AE являются строковыми переменными, а макрос reldays рассчитан на использование числовых
data ae_reldays; set ae; if not missing(AESTDTC) then AESTDT = input(AESTDTC, is8601da.); if not missing(RFSTDTC) then RFSTDT = input(RFSTDTC, is8601da.); %reldays(AESTDT, RFSTDT, AESTDY) drop AESTDT RFSTDT; run;
Итак, внутри дата степа мы вызываем макрос reldays с помощью знака ‘%’, и передаем ему такие параметры:
- в качестве первого параметра date – AESTDT;
- в качестве второго параметра rel_date – RFSTDT;
- в качестве третьего – AESTDY.
Как работает этот код.
Если утрировать, SAS “подставляет” на место вызова %reldays(AESTDT, RFSTDT, AESTDY) код макроса, затем, на места макро переменных “подставляет” их значения, и только потом, выполняет получившийся датастеп. В логе можно видеть, как это происходит (опция MPRINT должна быть активна)

Результатом выполнения будет датасет ae_reldays:

Мы познакомились с понятиями макро переменных и макро операторов, рассмотрели несколько примеров их использования, а также создали и применили простейший макрос. Далее углубимся еще немного в эту тему.
Macro переменные
Макро переменные могут быть локальными и глобальными.
- Локальные – это переменные, которые действительны только в теле макроса (в куске кода от оператора %macro до оператора %mend).
- Глобальные макро переменные действительны в течение всей сессии SAS, как в открытом коде, так и внутри любого макроса (только если локальная переменная с тем же именем временно не “перекрыла” глобальную).
Рассмотрим еще способы создания макро переменных и изучим области их действия на примерах.
Оператор %GLOBAL создает глобальные макро переменные с пустыми значениями. Чтобы задать этим макро переменным другие значения, можно использовать оператор %LET. Оператор %GLOBAL можно использовать как в открытом коде, так и в теле макроса – результат будет тот же.
Оператор %LOCAL создает локальные макро переменные с пустыми значениями. Этот оператор не может быть использован в открытом коде, только в теле макроса. Для переопределения значений так же используется оператор %LET.
В открытом коде мы имеем доступ только к глобальным переменным. В теле макроса доступны как глобальные, так и локальные. Локальные и глобальные макро переменные с одним и тем же именем не конфликтуют. Это значит, что в теле макроса может быть создана макро переменная с тем же именем, что и глобальная, при этом значение глобальной переменной останется неизменным, а внутри макроса SAS будет использовать значение локальной переменной.
Уже известный вам оператор %LET создает новую макро переменную, но только в том случае, если ранее не существовало переменной с таким именем, в противном случае – просто переопределяет значение уже существующей. Причем, если это тело макроса, то, сперва, оператор попытается переопределить значение локальной макро переменной, если же локальной с таким именем не существует, то переопределяется значение глобальной, и если не существует глобальной – будет создана новая локальная макро переменная.
Поскольку в открытом коде локальные переменные недоступны, оператор %LET в открытом коде создает или переопределяет только глобальные макро переменные.
Несколько слов о синтаксисе:
%let variable_name = <value>;
На месте <value> может быть как обычная строка, так и выражение содержащее другие макро переменные. Если опустить <value>, то значение макро переменной будет пусто
Например,
%let var1 = "PINK"; в результате, var1 = "PINK"
%let var2 = color; в результате, var2 = color
%let var3 = The &var2 is &var1; в результате, var3 = The color is "PINK"
Оператор %PUT позволяет увидеть в логе значение любой макро переменной, если она существует.
Рассмотрим пример, когда глобальная и локальная макро переменные имеют одно и то же имя:
*Using a Local Variable with the Same Name as a Global Variable; %global variable1 variable2; %let variable1 = 1; %let variable2 = 1; %macro test_macro; %put ***** Beginning TEST_MACRO *****; %local variable1 variable3; %let variable1 = 2; %let variable2 = 2; %let variable3 = 2; %put The values of variables inside TEST_MACRO are:; %put local variable1 = &variable1; %put global variable2 = &variable2; %put local variable3 = &variable3; %put ***** Ending TEST_MACRO *****; %mend test_macro; %put The values of variables in open code before submitting TEST_MACRO are:; %put variable1 = &variable1; %put variable2 = &variable2; %put variable3 = &variable3; %test_macro %put The values of variables in open code after submitting TEST_MACRO are:; %put variable1 = &variable1; %put variable2 = &variable2; %put variable3 = &variable3;
В программе:
- Создаются две глобальные макро переменные, variable1 и variable2, им присваивается значение 1;
- В макросе test_macro создаются две локальные макро переменные, variable1 и variable3. Им обоим присваиваются значение 2. Так же обратите внимание, что переменной variable2 так же присваивается значение 2.
Теперь, подумайте и определите, какими будут значения трех макро переменных variable1, variable2 и variable3 до, после и во время выполнения макроса test_macro. Теперь проверьте ваш ответ, посмотрев в лог программы:

Что еще необходимо знать о макро переменных?
Макро переменная – это всегда строка. Рассмотрим код:
%let p = 3.14; %let radius = 50; %let square = &p * (&radius**2); %put square = □
Можно было бы ожидать, что макро переменная square будет содержать результат выполнения арифметических действий, square = 3.14 * (50**2) = 7850. Но это не так.
В логе мы увидим,

Если же мы хотим в переменной square хранить площать круга, то мы должны «заставить» SAS в процессе замещения макро переменных их значениями преобразовать строку в число и выполнить арифметические действия. Это делается с помощью макро функций %EVAL и %SYSEVALF. Подробнее о них, и о других макро функциях смотрите здесь Macro Functions.
%let square = %sysevalf(&p *(&radius**2)); %put square = □
Лог:

Стоит отметить, что SAS в некоторых случаях умеет автоматически выполнять это преобразование. Например, в дата степе, когда значение числовой переменной вычисляется с помощью макро переменной:
data circles; input circle_id $ radius; square = &p*(radius**2); datalines; 01 50 02 23 03 65 04 35 05 15 ; run;
Тут макро переменная &p преобразовалась в число без дополнительных усилий.
Еще несколько особенностей, о которых нужно помнить, работая с макро переменными.
- SAS прекрасно распознаёт макро переменные внутри двойных кавычек. Более того, внутри двойных кавычек работают макро функции, макро операторы и сами макросы. А вот макро код в одинарных кавычках не распознается и обрабатывается как обычный текст.
- Обычно SAS сам определяет где в коде макро переменная: началом служит &, а окончанием – любой невалидный символ SAS-имени. Например, в коде %let var = &sysdate-date; макропеременная &sysdate определится и преобразуется в соответствующую строку, поскольку ‘-’ не может являться частью имени в SAS. Мы получим макро переменую var = 23SEP16-date. А вот такой вариант: %let var = &sysdatedate; не даст нам нужного результата, поскольку макро переменной &sysdatedate не существует. Таким образом, в некоторых случаях, нам необходимо «подсказать» системе где заканчивается имя нужной макро переменной. Для этого служит точка. То есть, если выполнить %let var = &sysdate.date;, то получим макро переменную var = 23SEP16date.
Рассмотрим пример.
%let date_format = date9; data ae; set ae; if not missing(AESTDTC) then AESTDT = input(AESTDTC, is8601da.); format AESTDT &date_format..; run;
Обратите внимание на запись format AESTDT &date_format..; Тут мы хотим наложить на переменную AESTDT, формат, который задан в макро переменной &date_format. Если мы поставим одну точку после имени: format AESTDT &date_format.; - эта точка «скушается» SASом в счет имени самой макро переменной. Выполним такой код и обратим внимание на лог:
data ae; set ae; if not missing(AESTDTC) then AESTDT = input(AESTDTC, is8601da.); format AESTDT &date_format.; run;
Лог программы (опция SYMBOLGEN должна быть активна):

То есть, SAS просто не будет знать, что date9 - это формат.
%Macro Statement
Как и макро переменные, макросы – это, по сути, текст, который будет подставлен в код там, где макрос был вызван. Конечно, по сравнению с макро переменными, у макросов больше возможностей:
- Макросы могут содержать определенные макро операторы, которые позволяют контролировать какой именно текст будет сгенерирован в результате работы макроса
- Макросы могут принимать входящие параметры.
Чтобы иметь возможность воспользоваться макросом, его необходимо сначала определить. Пример того, как это делается, вы уже видели:
%macro reldays(date, rel_date, rel_days); &rel_days = &date - &rel_date + (&date >= &rel_date); %mend reldays;
Поговорим подробнее о синтаксисе.
После оператора %macro в обязательном порядке следует имя макроса (валидное имя SAS). Затем в круглых скобках параметры. Параметров в определении макроса может и не быть. Заканчивается тело макроса всегда оператором %mend, имя макроса после ключевого слова mend – опционально, его можно не указывать, тем не менее, это считается хорошим стилем программирования.
Параметры бывают двух типов: позиционными (positional) и ключевыми (keywords). В примере выше использовались позиционные, поэтому при вызове этого макроса важнейшую роль играет то, в каком порядке будут заданы параметры.
Несколько примеров вызова этого макроса:
-
%reldays(AESTDT, RFSTDT, AESTDY). Такой вызов означает, что макро переменная
date = AESTDT,
rel_date = RFSTDT,
rel_days = AESTDY
-
%reldays(AESTDT, AESTDY, RFSTDT). Такой вызов означает, что макро переменная
date = AESTDT,
rel_date = AESTDY,
rel_days = RFSTDT.
-
%reldays(AESTDT, AESTDY). Такой вызов означает, что
date = AESTDT,
rel_date = AESTDY,
rel_days = .
-
%reldays(AESTDT, , AESTDY). Такой вызов означает, что
date = AESTDT,
rel_date = ,
rel_days = AESTDY.
-
%reldays Такой вызов означает, что
date = ,
rel_date = ,
rel_days = .
Все приведенные выше варианты абсолютно допустимы и не приведут к ошибкам на этапе компиляции программы.
Ключевые параметры задаются со знаком равно после имени параметра. Например,
%macro reldays(date=, rel_date=RFSTDTC, rel_days=); &rel_days = &date - &rel_date + (&date >= &rel_date); %mend reldays;
После знака ‘=’ можно указать «значение по-умолчанию», то есть то значение, которое будет использоваться в случае, если при вызове макроса не будет указан этот параметр. Порядок, в котором вы указываете параметры при вызове макроса, здесь не важен, поскольку вы явно указываете, какому параметру присвоить данное значение.
Например,
-
%reldays(date=AESTDT, rel_days=AESTDY, rel_date=RFSTDT). Такой вызов означает, что макро переменная
date = AESTDT,
rel_date = RFSTDT,
rel_days = AESTDY
-
%reldays(date=AESTDT, rel_days=AESTDY). Такой вызов означает, что макро переменная
date = AESTDT,
rel_days = AESTDY,
rel_date = RFSTDT (сработало «значение по-умолчанию»).
-
%reldays(rel_days=AESTDY). Такой вызов означает, что
date =,
rel_days = AESTDY,
rel_days = RFSTDT.
-
%reldays(AESTDT). Такой вызов приведет к ошибке, поскольку при определении макроса позиционных параметров не было объявлено.
При объявлении макроса часть параметров могут быть позиционными, а часть ключевыми. Самое главное, что при этом нужно помнить, – это то, что сначала должны быть перечислены все позиционные параметры, а потом все ключевые. Например,
%macro reldays(date, rel_date=RFSTDTC, rel_days=); &rel_days = &date - &rel_date + (&date >= &rel_date); %mend reldays;
Тут у нас один параметр (date) – позиционный, а два последующих (rel_date и rel_days) – ключевые. При вызове этого макроса первым всегда должно идти значение для параметра date. Например, вызов %reldays( , rel_days=AESTDY) будет корректным, а вот %reldays(rel_days=AESTDY, RFSTDT) вызовет ошибку.
Макро операторы
Как уже было замечено ранее, макросы могут содержать некие макро операторы, которые помогают контролировать, какой текст будет сгенерирован макро процессором и подставлен на место вызова макроса.
Вот список наиболее часто используемых (полный список тут Macro Statements):
%* Macro Comment Statement
%DO Statement
%DO, Iterative Statement
%DO %UNTIL Statement
%DO %WHILE Statement
%END Statement
%GLOBAL Statement
%IF-%THEN/%ELSE Statement
%LET Statement
%LOCAL Statement
%MACRO Statement
%MEND Statement
%PUT Statement
Не так уж и много, как может показаться, тем не менее, этих операторов, в купе с макро функциями, достаточно для большинства макросов.
С операторами %GLOBAL, %LET, %LOCAL, %MACRO, %MEND, %PUT вы уже знакомы.
С операторами %DO, %DO %UNTIL, %DO %WHILE, %END, %IF-%THEN/%ELSE познакомиться не сложно, поскольку они аналогичны тем же операторам из SAS BASE. Важнее всего понять, чем они отличаются от обычных, и когда ими пользоваться.
Усовершенствуем наш макрос reldays так, чтобы можно было ним пользоваться и для строковых переменных-дат.
%macro reldays_iso(date, rel_date, rel_days, dates_iso=1); %local date_num rel_date_num; %if &dates_iso %then %do; if not missing(&date) then date_num = input(&date, is8601da.); if not missing(&rel_date) then rel_date_num = input(&rel_date, is8601da.); format date_num rel_date_num date9.; %let date_num = date_num; %let rel_date_num = rel_date_num; %end; %else %do ; %let date_num = &date; %let rel_date_num = &rel_date; %end; &rel_days = &date_num - &rel_date_num + (&date_num >= &rel_date_num); %mend reldays_iso;
Итак, начнем с самого начала.
%macro reldays_iso(date, rel_date, rel_days, dates_iso=1);
Новый макрос reldays_iso имеет 4 параметра: первые три – позиционные (как было и раньше), и четвертый, новый, dates_iso – отвечает за то, с датами какого типа будет работать макрос. По умолчанию задано значение 1, что соответствует тому, что даты &date, &rel_date являются строковыми переменными в формате iso.
Работа макроса основана на том, что в зависимости от значения параметра dates_iso мы задаём значения для новых локальных макро переменных &date_num и &rel_date_num, и считаем дни известным нам способом.
Какие же значения могут принимать эти новые макро переменные?
Если мы работаем с числовыми датами, то есть &dates_iso ne 1, то новые макро переменные будут просто равны тем, что пользователь макроса передал в качестве параметра:
%let date_num = &date;
%let rel_date_num = &rel_date;
Если же пользователь передает в качестве параметров строковые переменные с датами в формате iso и не забывает при этом задать dates_iso=1, то в датасете будут созданы новые числовые переменные, date_num и rel_date_num, которые будут хранить в себе числовой эквивалент строковых дат. Имена этих новых числовых переменных будут присвоены локальным макро переменным &date_num и &rel_date_num:
if not missing(&date) then date_num = input(&date, is8601da.); if not missing(&rel_date) then rel_date_num = input(&rel_date, is8601da.); format date_num rel_date_num date9.; %let date_num = date_num; %let rel_date_num = rel_date_num;
Таким образом, определив значения макро переменных &date_num и &rel_date_num, становится возможным выполнить оставшийся код, &rel_days = &date_num - &rel_date_num + (&date_num >= &rel_date_num);
Применим новый макрос в датасете AE и создадим переменную AESTDY:
data ae_reldays2;
set ae;
%reldays_iso(AESTDTC, RFSTDTC, AESTDY);
run;
Лог выполнения дата степа:

Результат:
