5

我最近继承了一个看起来像这样的 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代码 - 它属于我的公司,我认为他们不希望它出现在 SO 上。

我尝试像调用%sas_sftp宏一样调用%ComplicatedStuff宏(CALL EXECUTE在同一数据步骤中作为第二个,作为第二个数据步骤),但只有第一个文件(大约 30 个)会到达目的地。当我查看日志时,看起来第二个宏在 ftp 完成之前开始执行(ftp 管道或其他任何东西在下一个 ftp 开始之前还没有被释放),所以随后的 ftp 只是默默地失败了由于资源不可用(我想)。

我认为 EXECUTE 基本上会将我的宏调用排队,然后像它们按顺序位于代码中一样执行它们(就像它们最初一样) - 一次一个。显然还有其他事情正在发生,因为虽然上面的第一种方法没有问题,但我的动态解决方案失败了。我倾注了CALL EXECUTE: How and WhySAS 文档,但恐怕我只是不明白他们在说什么。

我最终确实找到了一个解决方法(或者更确切地说,一位同事找到了一个),我在下面发布了一个“答案”,但我真的很想有人解释 EXECUTE 函数及其工作原理。

为什么我的第一次尝试,使用CALL EXECUTE,没有工作?

4

3 回答 3

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;
于 2013-07-10T21:16:53.667 回答
4

CALL EXECUTE 的工作方式与您在社区 wiki 中的代码类似,除了一些与时间相关的特定问题。我遇到的最常见问题是,当我在做一个包含定义宏变量的东西的宏时,例如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 未定义的消息,第二次和以后的时间您将收到所有 age=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;

我没有发现调用执行与数据生成代码的 PROC SQL 宏列表解决方案一样有用,除非在数据步骤中构建代码更容易并且我没有做任何导致计时问题的事情。

于 2013-07-10T21:41:25.387 回答
2

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;
于 2013-08-30T22:44:08.127 回答