带有 PROC SQL 的 SAS 中的函数样式宏?

function style macro in SAS with PROC SQL?

是否可以使这种形式的宏起作用?

    %macro tableMath(input1,input2);
    %local result;
    proc sql; ---some code here using inputs--- quit;
    proc sql; ---more code here--- quit;
    proc sql;
    select something into: result
    quit;
    &result
    %mend;

我想 运行 对数据集的每次观察进行一些相当复杂的逻辑,并且在我之前使用过的任何其他语言中,这样做的方法是将它封装在一个函数中 returns 每次被调用时都是一个结果——但是我不确定如何在 SAS 中执行此逻辑。

编辑:input1 和 input2 将是数据集的列,结果将用于在程序另一部分的某个其他宏中创建新列。我不需要特定的代码解决方案我只是不明白你应该如何在 SAS 中需要 return 值的地方执行传统函数逻辑...

你说你想:

run some fairly complicated logic on each observation of a dataset

为此,您应该使用 SAS 语言而不是宏处理器或 PROC SQL。您可以使用数据步骤。或者对于更复杂的逻辑,您应该查看 PROC DS2。

宏函数没有 return 值。一个宏函数可以'emit'源代码

  • 这是一个或多个步骤,
  • 那是代码被合并到语句中的片段,
  • 这是一个或多个步骤的一部分的语句,
  • 等等

对于 'do' SQL 中的内容的情况,您可以编写 SQL views 然后

  • %sysfunc(open())
  • 打开
  • 已处理
    • %sysfunc(set())
    • %sysfunc(getvarn())
    • %sysfunc(getvarc())

并非所有 SQL 功能都可以通过此技术使用 -- select something into :result 必须是具有 select something 的视图,而宏将 getvarc 用于读取结果。

以 open/set/get 方式完成的访问不会导致出现步骤边界,因此宏处理可以继续其逻辑并最终发出源代码以供片段级别使用。 (消费者是处理宏代码的SAS执行者,隐式编译运行SAS步骤)

正如 Richard 所写,函数式宏发出 SAS 代码。开发函数式宏的一般规则是它们只包含宏语言语句。将发出它们包含的任何 SAS 代码。从历史上看,这使得 difficult/annoying 编写函数式宏来像使用 DATA 步一样处理数据。幸运的是,SAS 添加了一个函数 DOSUBL,这使得编写在 "side session" 中执行 SAS 代码并发出结果的函数式宏变得更加容易。参见 Rick Langston's paper

这是一个函数式宏的示例,它使用 DOSUBL 计算 table 中的记录数并发出计数。 (这是一种非常低效的获取记录计数的方法,只是在SQL中做某事的一个例子)。

%macro SQLcount(table);
  %local rc emit; 

  %let rc=%sysfunc(dosubl(%nrstr(
     proc sql noprint;
      select count(*) into :emit trimmed
      from &table
     quit;
  )));

  &emit 
%mend ;

它可以像这样使用:

proc sql ;
  select name
        ,%SQLcount(sashelp.shoes) as ShoeCount  /*emits 395*/
  from sashelp.class
  ;
quit ;

以上步骤运行时,会return19行来自sashelp.class的名字,每行ShoeCount的值为395。注意宏 SQLcount 只执行了一次。当 PROC SQL 步骤正在 compiled/interpreted 时,可以看到对 SQLcount 的调用,并且执行宏并发出 395。该步骤变为:

proc sql ;
  select name
        ,395 as ShoeCount  /*emits 395*/
  from sashelp.class
  ;
quit ;

DOSUBL 使用 "side session" 来执行代码,这允许您在主会话解释 PROC SQL 步骤时在副会话中执行 PROC SQL 步骤。

我无法从你的问题中判断出那种用例是否是你想要的。您可能需要一个函数样式的宏,您可以在其中将值从 table 传递给它,并让宏在每个值和 return 上执行。假设您有一个 table,它是 table 名称的列表,并且想使用 SQL 来获取每个 table:

中的记录数
data mytables ;
  input table . ;
  cards ;
sashelp.shoes
sashelp.class
sashelp.prdsale
;
quit ;

您可以通过使用 resolve() 函数从数据构建宏调用,延迟宏的执行直到 SELECT 语句执行:

proc sql ;
  select table
        ,resolve('%SQLcount('||table||')') as count
  from mytables
  ;
quit ;

这样,SQLcount 将被调用 3 次,并将 return 每个数据集中的记录数。

table                 count
---------------------------
sashelp.shoes         395
sashelp.class         19
sashelp.prdsale       1440

解释 PROC SQL 步骤时看不到宏调用,因为它被单引号隐藏了。解析函数然后在 SELECT 语句执行时调用宏,将 table 的值作为参数值传递,宏发出记录计数。这类似于使用数据驱动宏调用的 CALL EXECUTE 方法。

听起来您可能想使用 proc fcmp 创建 FCMP 函数。这基本上是一种创建您自己的 SAS 函数的方法,可以在 proc sqldata 步骤中使用。例如:

/******************************************************************************
** PROGRAM:  COMMON.FCMP_DIV.SAS
**
** DESCRIPTION: PERFORMS A MATHEMATICAL DIVISION BUT WILL RETURN NULL IF THE
**              NUMERATOR OR DENOMINATOR IS MISSING (OR IF THE DIVISOR IS 0).
**
******************************************************************************/

proc fcmp outlib=common.funcs.funcs;

  function div(numerator, denominator);

    if numerator eq . or denominator in (0,.) then do;
      return(.);
    end;
    else do;
      return(numerator / denominator);
    end;

  endsub;
run;

示例用法(示例是数据步骤,但在 SQL 中同样有效):

data x;
  x1  = div(1,0);
  x2  = div(1,.);
  x3  = div(1,1);

  x4  = div(0,0);
  x5  = div(0,.);
  x6  = div(0,1);

  x7  = div(.,0);
  x8  = div(.,.);
  x9  = div(.,1);

  put _all_;
run;