【问题标题】:SAS Call execute output changes every time I run itSAS Call 每次运行时都会执行输出更改
【发布时间】:2017-05-19 20:05:21
【问题描述】:

我正在运行一个在数据步骤中调用执行的宏。 我想从我的数据步骤中得到以下内容:

获取一个表,为每个现有列添加一个新列(通过宏),最后添加一个新列,该列是其他两个列的总和。 我想没有宏也是可能的,但我想要这样,因为我是 SAS 新手,想了解宏的逻辑并调用执行。

假设我有下表:

data values;
input a1 a2 b1 b2;
datalines;
1 0 3 10
0 5 6 11
7 8 9 0
;
run;

还有这个宏:

%macro loop1(myDataset);  
proc contents data=&myDataset. out=Col_Names (keep=Name) noprint;
run;
proc sql noprint; 
select count(Name) into :length from Col_Names;
quit;                                                 
     %do j = 1 %to &length; 
            data &myDataset.;
            set &myDataset.; 
            n&j=0;
            run;                       
     %end;
%mend;

然后以下数据步骤在我运行它的前 3 次创建不同的输出: (每次运行后,我当然会使用数据线重新运行原始数据步骤)

data values;
set values;
if _n_=1 then call execute('%loop1(values);');
test=sum(a1,a2);
run;

第一次运行出现错误:

警告:未解析明显的符号引用 LENGTH。错误:一个 在 %EVAL 函数或 %IF 条件中找到字符操作数 其中需要数字操作数。条件是: &length 错误:%DO J 循环的 %TO 值无效。错误:宏 LOOP1 将停止执行。

第二次运行的结果正是我想要的:

a1、a2、b2、b2、test、n1、n2、n3、n4 列

从第三次运行开始,输出保持不变:

a1、a2、b2、b2、test、n1、n2、n3、n4、n5 列

里面有不想要的n5

我应该如何更改才能始终获得第二次运行的输出?

【问题讨论】:

    标签: macros sas execute sas-macro datastep


    【解决方案1】:

    失败的原因是当您的数据步骤运行时 SAS 正在运行宏,并将宏生成的代码推送到堆栈中。然后在数据步骤完成后运行实际生成的 PROC 和 DATA 步骤。

    因此,当您的宏运行时,它会生成 PROC SQL 步骤,但该步骤尚未执行,因为您的数据步骤仍在运行。然后该宏运行%DO 循环并生成错误,因为宏变量LENGTH 不存在,或者使用数据步骤开始之前存在的宏变量的值。

    为了防止这种情况将宏调用包装在%NRSTR() 中,以便宏调用本身被压入堆栈以在数据步骤停止后运行。

    call execute('%nrstr(%loop1)(values);');
    

    您的实际示例根本不需要 CALL EXECUTE。只需在数据步骤运行后调用宏即可。

    data values;
      set values;
      test=sum(a1,a2);
    run;
    %loop1(values);
    

    或者,如果您不想为新的 TEST 变量创建标志变量,请在数据步骤之前运行宏。

    【讨论】:

      【解决方案2】:

      为什么会失败:

      您的代码并没有在您认为的那一刻将变量“n1”等添加到“values”数据集中。它会在最后一个数据步骤之后安排您的宏,这不是您想要的。

      例如,尝试将分配添加到您的最后一个数据步骤

      data values;
          set values;
          *&new_columns;
          if _n_=1 then call execute('%loop1(values);');
      
          test=sum(a1,a2);
          n2=9874;
      run;
      

      你会看到它没有任何效果,因为当你的宏运行时你的“n2”值被覆盖了;

      你可以做什么:

      使用select <something> into :<variable> separated by <separator>,您可以创建一个包含赋值的宏变量。

      proc contents data=values noprint out=Col_Names(keep=varnum);
      run;
      proc sql noprint;
          select 'n'|| strip(put(varnum, 8.)) ||'=0'
          into :new_columns separated by ';'
          from col_names;
      quit;
      

      您可以在数据步骤中使用此变量:

      data values;
          set values;
          &new_columns;
      
          test=sum(a1,a2);
          n2=9874; ** Now this has effect **;
      run;
      

      【讨论】:

      • 当然最好避免使用宏,但 OP 确实说他想要“完全这样”
      • 它永远无法“完全以这种方式”工作,因为 call execute 仅在编译数据步骤时运行,此时您不能再向数据步骤添加变量。 (从技术上讲,程序向量是在那个时候创建​​的。)
      【解决方案3】:

      使用call execute 调用宏时,建议按照以下用法说明将它们包装在%nrstr() 中:

      http://support.sas.com/kb/23/134.html

      这可以防止过早的宏执行——或者至少,强制它等待任何依赖的宏变量准备好,即INTO: 子句中的length 变量。

      data values;
        set values;
        if _n_=1 then call execute('%nrstr(%loop1(values);)');
        test=sum(a1,a2);
      run;
      

      为了得到你想要的结果,你还需要在你的SQL过程中排除'test'变量,如下:

      proc sql noprint; 
      select count(Name) into :length from Col_Names
        where upcase(name) ne 'TEST';
      

      【讨论】:

      • 谢谢!但是结果直接是第三个,我想要第二个。
      • 但是如果在数据步骤中它是在我的宏之后创建的,那么 'test' 怎么会出现在 proc sql 中呢?在这种情况下,宏现在可以工作,但每次有新列时我都必须对其进行调整。没有别的办法了吗?
      • 调用执行不会立即启动您的宏。它(有点)在您的数据步骤之后安排它。 查看您的日志。 它说您首先使用 4 个变量 'a1' 、 'a2' 、 'a3' 、 'a4' 创建 'values' ,然后使用 5 个变量创建它(添加 'test '),然后才提到'%loop1'。 试试这个:在数据步骤的末尾添加drop n1 ,n2 n3 n4;。您将看到它们在最终结果中,因此它们是稍后创建的,同时在您的数据步之后运行“%loop1”。
      • 正如@DirkHorsten 提到的,宏在数据步骤之后执行。另一种方法是将proc sql 修改为仅保留您需要的变量,例如where substr(upcase(name),1,1) in ('A','B')
      • 当然,这整件事非常做作。我建议尽可能避免使用call execute,同时尽量避免覆盖数据集 - 每次都创建新数据集,以便跟踪差异!
      猜你喜欢
      • 1970-01-01
      • 2020-03-02
      • 1970-01-01
      • 1970-01-01
      • 2018-09-21
      • 1970-01-01
      • 1970-01-01
      • 2021-12-06
      • 1970-01-01
      相关资源
      最近更新 更多