EN
【技术】更易懂!使用CALL EXECUTE驱动程序!
2025-02-20 09:47

在SAS编程中,我们通常使用%DO语句来循环宏参数的值,从而多次调用并执行具有不同宏参数的宏程序,然而,使用%DO语句通常涉及在循环中使用宏变量的间接引用。DATA步的CALL EXECUTE 提供了一种不使用%DO语句执行宏程序的替代方法,它消除了宏变量的间接引用,使得程序更加简洁易懂。


 一、语法

CALL EXECUTE(参数);

CALL EXECUTE中的参数可以是以下三种情形之一:

  • 用引号括起来的字符串。单引号内的参数在程序执行期间解析。在构造DATA步时,双引号内的参数会解析。

  • DATA步字符变量的名称,其值是要生成的文本表达式或SAS语句。不要将DATA步字符变量的名称括在引号中。

  • 由DATA步解析为宏文本表达式或SAS语句的字符表达式。


二、CALL EXECUTE如何工作

首先我们来了解SAS系统如何处理代码,如下图所示。

1.png

SAS代码提交运行后,SAS代码立即进入Input Stack,Input Stack是一个虚拟组件,它会保存代码至系统将其推入下一个名为Word Scanner的组件。在Word Scanner中会进行标记化,它根据某些规则将SAS代码进行组装标记。

在这里,具有特殊字符“&”和“%”的标记可以触发Macro Processor。Macro Processor利用Symbol Table或Macro Catalog中预先存储的信息,通过与Input Stack和Word Scanner的交互来处理宏表达式(宏变量或宏指令)。如果Word Scanner没有发现此类特殊字符,则标记将移动到Compiler,Compiler在收到数据步边界标记(例如RUN、QUIT)后编译代码。

在此编译阶段,Compiler检查代码语法,设置PDV并执行KEEP和DROP等某些语句。编译后的代码会继续移动并最终到达Execution Module,它会指示系统对PDV做更多的事情,以创建新的数据集或修改旧的数据集。

CALL EXECUTE的神奇之处在于,当包含CALL EXECUTE的“父”代码仍在Execution Module中时,它能够从PDV中提取数据值,用它们构建新的“子”SAS代码,然后将新的“子”SAS代码发送回Input Stack进行存储。新构造的代码可以是宏表达式或数据步语句。在Input Stack中,“子”代码以创建的顺序排列。当“父代码”执行完毕后,它们就开始依次沿着这条“装配线”移动。通过这种方式,可以利用driver table中的数据值来生成与数据相关的后续代码,以实现数据驱动的编程设计。


三、参数解析

通常需要引号来区分参数中的“静态”部分(字符串)和“动态”部分(字符表达式),不同的引号会导致CALL EXECUTE以不同的方式解析参数。但在实际编程实践中,如果后续代码仅与数据步语句相关,则两种引号都会给出相同的预期“子”代码。如果后续代码与宏相关,双引号可能会导致意外结果,我们对此进行详细讨论。

1、参数字符串没有宏或宏变量

如果CALL EXECUTE的参数字符串没有宏或宏变量,则参数字符串为在引号内的字符常量和SAS变量的简单连接,SAS变量在每次DATA步迭代时都会被CALL EXECUTE替换为它们的值,并构建SAS“子”代码附加到当前“父”代码所在DATA步后。DATA步完成后,SAS“子”代码将按照创建顺序依次执行。

/*example 1 Argument string has no macro or macro variable reference*/

/* Creating a driver table*/

data tablelist;

length tname $8;
Input tname;
datalines;

cars

class

classfit

;

run;


/*Loading multiple tables*/

data _null_;

   set tablelist;

   call execute(cat(

      'data ',strip(tname),';',

         'set sashelp.',strip(tname),';',

      'run;'));

run;

在这个例子中,我们使用DATA步循环遍历driver table(tablelist),对于tname列的每个值,都会在DATA步之后生成并执行一个新的数据步。

2.png

2、参数字符串在双引号中引用了宏变量

如果CALL EXECUTE的参数具有双引号中的宏变量引用,参数作为字符表达式在“父”代码的编译阶段解析。

/*example 2 Argument string has macro variable reference in double quotes*/

%let olib = sashelp;

%let nlib = work;


data _null_;

   set tablelist;

   call execute(cats(

      "data &nlib..",tname,';',

         "set &olib..",tname,';',

      'run;'));

run;

3、参数字符串在单引号中引用了宏或宏变量

如果CALL EXECUTE的参数有单引号中的宏或宏变量引用,参数作为字符串在“父”代码的编译阶段不会被解析,并在执行阶段以字符串形式构造“子”代码,最终在“子”代码的编译阶段解析。

/*example 3 Argument string has macro or macro variable reference in single quotes*/

%let olib = sashelp;

%let nlib = work;


data _null_;

   set tablelist;

   call execute(cats(

      'data &nlib..',tname,';',

         'set &olib..',tname,';',

      'run;'));

run;

4、时间因素

如果宏在运行时分配宏变量,那么在CALL EXECUTE生成代码之前,会先在“父”代码的编译阶段解析这些宏变量,导致warning的出现。

/*example 4 Timing considerations*/

%macro onetable (tname);

   proc contents data=sashelp.&tname out=one(keep=name) noprint;

   run;


   proc sql noprint;

      select name into :varlist separated by ' ' from one;

   quit;

   %put &varlist;


   data work.&tname;

     retain &varlist;

     set sashelp.&tname end=last nobs=n;

     if last then call symput('n',strip(put(n,best.)));

   run;

   %put Table &tname has &n observations.;

%mend onetable;


/*without %nrstr */ 

data _null_;

   set tablelist;

   call execute('%onetable('!!strip(tname)!!');');

run;

3.png

为了避免过早解析宏变量,在CALL EXECUTE生成代码之前,我们可以在“父”代码的编译阶段抑制宏变量的解析。为此,我们可以使用%nrstr宏函数屏蔽&和%字符。在这种情况下,宏变量将在“子”代码的编译阶段解析。

/*with %nrstr */ 

data _null_;

   set tablelist;

   call execute('%nrstr(%onetable('!!strip(tname)!!'));');

run;

4.png

5、CALL EXECUTE参数是SAS变量

CALL EXECUTE的参数可以是一个SAS变量,确切地说是字符变量。在这种情况下,CALL EXECUTE的行为与参数为单引号字符串时的行为相同。这意味着,如果宏或宏变量是参数值的一部分,则需要使用%nrstr宏函数对其进行遮蔽,以避免warning的出现。

/*example 5 CALL EXECUTE argument is a SAS variable*/

arg = '%nrstr(%mymacro(parm1=VAL1,parm2=VAL2))';

call execute(arg);

6、CALL EXECUTE实现完全由数据驱动程序

在上述示例中,我们使用driver table(tablelist)为每个数据步迭代检索单个宏参数的值。然而,我们不仅可以使用driver table动态地为一个或多个宏参数赋值,还可以控制在每个数据步迭代中执行哪个宏。下图说明了完全由数据驱动的SAS程序的过程:

5.png

总结  

CALL EXECUTE的使用,不仅可以消除在宏程序中使用迭代%DO循环和间接引用,使得程序直白易懂,还可以根据程序驱动表动态生成和运行SAS程序实现一定程度上的自动化。然而,知道何时使用CALL EXECUTE并理解宏编译的机制对于编写准确的宏程序至关重要。


参考文献  

https://blogs.sas.com/content/sgf/2017/08/02/call-execute-for-sas-data-driven-programming/

extension://idghocbbahafpfhjnfhpbfbmpegphmmp/assets/pdf/web/viewer.html?file=https%3A%2F%2Fpharmasug.org%2Fproceedings%2F2015%2FBB%2FPharmaSUG-2015-BB15.pdf

我们如何帮您呢?凯莱英临床(凯诺)专业团队为您尽快提供服务