我最近继承了一个看起来像这样的SAS程序:
%MACRO ComplicatedStuff( GroupId= );
%LET FileId = %SYSFUNC( OPEN( Work.BigDataSet ) );
%PUT 'Doing something really difficult with ' &GroupId.;
%LET CloseRC = %SYSFUNC( CLOSE( &FileId. ) );
%MEND ComplicatedStuff;
%ComplicatedStuff(GroupId=ABC1);
%ComplicatedStuff(GroupId=DEF2);
%ComplicatedStuff(GroupId=3GHI);
%ComplicatedStuff(GroupId=J4KI);
作为一个多方面的程序员,我看着这个并且想“我肯定能让这个变得更有活力”。果然,我能够使用CALL EXECUTE
开发我认为简单的解决方案:
DATA Work.IDs;
INPUT ID $4.
;
DATALINES;
ABC1
DEF2
3GHI
J4KI
RUN;
DATA Work.CommandDebug;
SET Work.IDs;
Command = CATS(
'%ComplicatedStuff(GroupId=', ID, ');'
);
CALL EXECUTE( Command );
RUN;
我很满意这个解决方案,直到将ComplicatedStuff生成的文件FTP到另一台服务器。我们的SAS服务器在Unix上运行,SAS管理员为我们创建了一个有用的小宏来调用名为%sas_sftp
(因为,我被告知,x
代码变得非常丑陋)。不幸的是,我无法发布%sas_sftp
代码 - 它属于我的公司,我认为他们不想要它。
我尝试调用%sas_sftp
宏,就像调用%ComplicatedStuff
宏一样(在同一数据步骤中作为第二个CALL EXECUTE
,并作为第二个数据步骤),但只有第一个文件(大约30个)会到达目的地。当我查看日志时,看起来第二个宏在ftp完成之前开始执行(ftp管道,或者不管它是什么,在下一个ftp启动之前还没有释放),所以后续的ftps只是默默地失败了由于资源不可用(我推测)。
我认为EXECUTE基本上会将我的宏调用排队,然后执行它们就好像它们按顺序位于代码中一样(就像它们原来的那样) - 一次一个。很明显其他事情正在发生,因为虽然上面的第一种方法没有问题,但我的动态解决方案失败了。我倾注了CALL EXECUTE: How and Why和SAS documentation,但我恐怕只是不明白他们在谈论什么。
我最终找到了一个工作(或者更确切地说,一个同事找到了一个),我在下面发布了一个“答案”,但我真的很想有人解释EXECUTE功能以及它是如何工作的。
为什么我的第一次尝试使用CALL EXECUTE
无效?
答案 0 :(得分:4)
这是一个避免EXECUTE
功能的工作。我发布它作为对未来访问者的帮助,但它并没有真正回答我的核心问题。
下面的代码通过利用SQL INTO:语法创建我想要执行的命令的宏变量。然后我创建一个简单的宏,它基本上迭代宏变量并解析它们(导致语句执行就好像它们在源代码中一样)。
PROC SQL NOPRINT;
SELECT COUNT(*)
INTO :CommandCount
FROM Work.CommandDebug
;
SELECT Command
INTO :Command1 - :Command%LEFT(&CommandCount.)
FROM Work.CommandDebug
;
QUIT;
%MACRO ExeCommands;
%DO I = 1 %TO &CommandCount.;
&&Command&I.; /* Resolves to %ComplicatedStuff(GroupId=ABC1);, etc */
%END;
%MEND;
%ExeCommands;
答案 1 :(得分:4)
CALL EXECUTE与社区维基中的代码类似,除了与时间相关的一些特定问题。我遇到的最常见的问题是当我在做一个包含定义宏变量的宏的宏时,例如宏中的PROC SQL select into
然后创建宏中使用的宏文本 - 与你的答案不同。由于时序规则,直到CALL EXECUTE完成构造要执行的代码之后才会执行,这意味着代码中的值没有正确更改。
这是一个例子。
%macro mymacro(age=0);
proc sql noprint;
select quote(name) into :namelist separated by ',' from sashelp.class where age=&age.;
quit;
data name_age;
set sashelp.class;
where name in (&namelist.);
run;
proc print data=name_age;
var name age;
run;
%mend mymacro;
proc sort data=sashelp.class out=class nodupkey;
by age;
run;
好的,现在我有一个控制数据集(class
)和一个用来运行它的宏。这是call execute
。这不能正常工作;第一次运行时,您将收到有关& namelist未定义的消息,第二次和将来您将获得所有年龄= 16(最后一个年龄),因为这是宏变量定义为。
data _null_;
set class;
exec_val = cats('%mymacro(age=',age,')');
call execute(exec_val);
run;
这是sql宏调用。这可以按预期工作。
proc sql noprint;
select cats('%mymacro(age=',age,')') into :calllist separated by ' '
from class;
quit;
&calllist;
我没有发现调用execute对于数据生成代码的PROC SQL宏列表解决方案有用,除非在数据步骤中构造代码更容易,并且我没有做任何导致时序问题的事情。
答案 2 :(得分:1)
在CALL EXECUTE中进行宏评估计时的一个好方法是将代码放入临时文件中,然后包含该文件。当您想要提交已提交代码的硬拷贝时(仅将fileref切换为物理文件),这对于调试也很有用。
%macro mymacro(age=0);
proc sql noprint;
select quote(name) into :namelist separated by ',' from sashelp.class where age=&age.;
quit;
data name_age;
set sashelp.class;
where name in (&namelist.);
run;
proc print data=name_age;
var name age;
run;
%mend mymacro;
proc sort data=sashelp.class out=class nodupkey;
by age;
run;
filename blah temp;
data _null_;
set class;
file blah;
exec_val = cats('%mymacro(age=',age,')');
put exec_val;
run;
%include blah;
filename blah clear;