Этот пост я пишу скорее даже для себя. С другой стороны, подобных материалов по SAS Base на русском исчезающе мало, так что может и пригодится кому-то ещё.
Сегодня решая забавную задачку по работе, понял, что мне не хватает произвольной, собственной функции. Как её можно создать в SAS? Ну для начала там есть макросы. Но это не то. Макросы в SAS - это скорее метапрограммирование: ты пишешь код, который потом при запуске сгенерирует обычный SAS Base-код и уже он пойдёт на исполнение. А мне нужно было (не вдаваясь в подробности) формировать из одной таблицы другую по шаблону третьей. Уверен, это можно было реализовать вкраплением SQL-запроса (SAS это тоже позволяет делать), но моя мысль заключалась несколько в другом: как бы так объявить обычную функцию, которая бы преобразовывала дающиеся на вход данные, да ещё с участием данных из других таблиц?
Язык SAS Base/Macro - весьма своеобразная датаориентированная среда, и то, что в языках типа Java или PHP решается на раз, в SAS требует принципиально другого подхода. Поэтому пришлось попотеть. Схематичный исходник и пояснения - под катом:
/* === Начальные данные === */
data users; /* Таблица с юзерами, отсюда потом будем брать имена */
infile datalines;
input id :8. name :$100.;
return;
datalines;
1 Anton
2 Boris
3 Catarina
4 Dmitry
5 Ermak
;
run;
data base(drop=i j); /* Таблица-шаблон. По ней потом будем создавать табличку с именами. Эту таблицу вообще случайными данными забиваем */
array id{6} 8;
do j=1 to 10 by 1;
do i=1 to 6 by 1;
id{i} = floor(6*rand("Uniform")); /* от 0 до 5, 0 - чтобы некоторые поля были пустые на выходе*/
end;
output;
end;
run;
/* === Основная магия тут === */
%macro macro_GetNameByID(); /* Макрос для чтения нужной строки из таблицы USERS */
data _null_;
set users(where=(id=&id));
call symputx('name',name);
%let name=&name;
run;
%mend get_Name_by_ID;
proc fcmp outlib=work.funcs.my;
/* Собственно, сама функция */
function get_name_by_id(id) $; /* $ как признак того, что функция возвращает именно строку, а не число */
length name $100;
rc=run_macro('macro_GetNameByID',id,name); /* Функция вызывает макрос. Макрос, приём! */
return(name);
endsub;
run;
options cmplib=work.funcs;
/* --- Конец магического блока --- */
/* === Основная работа: берём таблицу BASE, и в соотвествии с числами в ней создаём таблицу NAMES === */
data names(drop=i j id:);
set base;
array name{6} $ 100;
array id{6} 8;
do i=1 to 6 by 1;
name{i} = get_name_by_id(id{i});
end;
run;
Что получается? Примерно вот такое непотребство:
WORK.USERS
|
1 | Anton |
2 | Boris |
3 | Catarina |
4 | Dmitry |
5 | Ermak |
WORK.BASE|
4 | 3 | 3 | 0 | 0 | 4 | 5 | 5 | 3 | 0 | 1 | 0 | 0 | 3 | 1 | 1 | 0 | 4 | 4 | 0 | 4 | 0 | 1 | 4 | 5 | 5 | 4 | 5 | 3 | 0 | 5 | 1 | 4 | 4 | 0 | 4 | 2 | 0 | 2 | 1 | 0 | 1 | 1 | 4 | 1 | 0 | 3 | 3 | 4 | 3 | 0 | 5 | 2 | 2 | 2 | 1 | 0 | 4 | 1 | 4 |
| WORK.NAMES|
Dmitry | Catarina | Catarina | | | Dmitry | Ermak | Ermak | Catarina | | Anton | | | Catarina | Anton | Anton | | Dmitry | Dmitry | | Dmitry | | Anton | Dmitry | Ermak | Ermak | Dmitry | Ermak | Catarina | | Ermak | Anton | Dmitry | Dmitry | | Dmitry | Boris | | Boris | Anton | | Anton | Anton | Dmitry | Anton | | Catarina | Catarina | Dmitry | Catarina | | Ermak | Boris | Boris | Boris | Anton | | Dmitry | Anton | Dmitry |
|