繁体   English   中英

SAS - 如何从 SAS 宏返回值?

[英]SAS - How to return a value from a SAS macro?

我想从我创建的 SAS 宏返回一个值,但我不确定如何。 该宏计算数据集中的观察数。 我想要返回的观察次数。

%macro nobs(library_name, table_name);
  proc sql noprint;
    select nlobs into :nobs
    from dictionary.tables
    where libname = UPCASE(&library_name)
      and memname = UPCASE(&table_name);
  quit;

  *return nobs macro variable;
  &nobs
%mend;

%let num_of_observations = %nobs('work', 'patients');

另外,我希望在宏中使用的&nobs宏变量是该宏的局部变量而不是全局变量。 我怎样才能做到这一点?

我将回答 Bambi 在评论中提出的核心问题:

我在这里主要关心的是如何从宏返回一个值。

我将在这里以一种重要的方式与德克争论。 他说:

SAS 宏插入代码。 它永远不会返回值,尽管在某些情况下您可以模仿函数

我不同意。 SAS 宏返回插入到处理流中的文本。 退货绝对是一个合适的术语。 当文本恰好是单个数字时,可以说它返回一个 value

但是如果宏除了该值之外只有宏语句,则它只能返回单个 意思是,每一行都必须以%开头。 任何不以%开头的东西都将被返回(并且一些以%开头的东西也可能被返回)。

所以重要的问题是,我如何从宏返回一个值


在某些情况下,像这种情况,完全可以只用宏代码。 事实上,在许多情况下,这在技术上是可行的——尽管在许多情况下,这比您应该做的工作要多。

杰克汉密尔顿的链接论文包含一个适合此处的示例。 他驳回了这个例子,但这主要是因为他的论文是关于在 NOBS 错误的情况下计算观察结果 - 无论是使用 WHERE 子句,还是在没有更新 NOBS 元数据的情况下修改数据集的某些其他情况。

在您的情况下,您似乎非常乐意信任 NOBS - 所以这个例子就行了。

返回值的宏必须恰好有一个语句,该语句不是宏语法语句,或者将值返回到处理流中的宏语法语句。 %sysfunc是这样做的语句示例。 %let%put%if等是不返回任何内容(本身)的语法语句; 所以你可以拥有尽可能多的那些。

您还必须有一个将值放入处理流的语句:否则您将根本无法从宏中获得任何信息。

这是第 3 页末尾 Jack 宏的精简版本,简化为删除他显示的nlobsf是错误的:

 %macro check;

   %let dsid = %sysfunc(open(sashelp.class, IS));
   %if &DSID = 0 %then
   %put %sysfunc(sysmsg());

   %let nlobs = %sysfunc(attrn(&dsid, NLOBS));

   %put &nlobs;

   %let rc = %sysfunc(close(&dsid));

 %mend;

该宏不是函数风格的宏。 它不会向处理流返回任何内容! 它对于查看日志很有用,但对于为您提供可以编程的值没有用处。 然而,对于函数风格的宏来说,这是一个好的开始,因为你真正想要的是&nlobs ,对吧?

 %macro check;

   %let dsid = %sysfunc(open(sashelp.class, IS));
   %if &DSID = 0 %then
   %put %sysfunc(sysmsg());

   %let nlobs = %sysfunc(attrn(&dsid, NLOBS));

   &nlobs

   %let rc = %sysfunc(close(&dsid));

 %mend;

现在这是一个函数风格的宏:它有一个不是宏语法语句的语句, &nlobs. 在一条简单的线上。

它实际上比你需要的更多; 还记得我说过%sysfunc向处理流返回一个值吗? 您可以删除该语句的%let部分,留下您

 %sysfunc(attrn(&dsid, NLOBS))

然后该值将直接放置在处理流本身中 - 允许您直接使用它。 当然,如果出现问题,调试并不容易,但我相信如果需要,您可以解决这个问题。 还要注意语句末尾没有分号——这是因为宏函数不需要分号来执行,而且我们不想返回任何无关的分号。

让我们保持良好的行为并添加一些%local来获得这个好和安全,并将数据集的名称作为参数,因为大自然憎恶没有参数的宏:

 %macro check(dsetname=);

   %local dsid nlobs rc;

   %let dsid = %sysfunc(open(&dsetname., IS));
   %if &DSID = 0 %then
   %put %sysfunc(sysmsg());

   %let nlobs = %sysfunc(attrn(&dsid, NLOBS));

   &nlobs

   %let rc = %sysfunc(close(&dsid));

 %mend;

 %let classobs= %check(dsetname=sashelp.class);

 %put &=classobs;

你有它:一个函数风格的宏,它使用nlobs函数来找出任何特定数据集中有多少行。

编写类似函数的宏的问题是什么?

即您可以用作%let myVar = %myMacro(myArgument)

  • 您可以像使用函数一样使用用户编写的宏,如果您所做的只是
    • 调用一些%doSomething(withSometing)类的宏函数
    • 使用%let someVar =语句为宏变量赋值
    • “返回”您的结果,通常通过编写&myResult. 在你的%mend之前的最后一行
  • 一旦您在宏中包含procdata步骤,这将不再起作用
  • 幸运的是,%sysFunc() 来拯救我们,所以我们可以使用任何数据阶跃函数
  • 这包括低级函数,如openfetchclose ,它们甚至可以访问您的数据
  • 书呆子可以用它做很多事情,但即使你是书呆子,你的老板也很少给你时间这样做。

我们如何解决这个问题? ,即我使用哪些构建块来解决这个问题?

  • proc fcmp允许在子程序或函数中打包一些数据步骤语句
  • 此函数用于数据步骤,可在%sysfunc()
  • 在此函数中,您可以调用run_macro以立即在run_macro执行任何宏

现在我们准备好实用的解决方案

第一步:写一个辅助宏

  • 没有参数,
  • 使用一些全局宏变量
  • 在全局宏变量中“返回”其结果

我知道这是不好的编码习惯,但为了降低风险,我们用前缀限定这些变量。 应用于问题中的示例

** macro nobsHelper retrieves the number of observations in a dataset
    Uses global macro variables:
        nobsHelper_lib: the library in which the dataset resides, enclosed in quotes
        nobsHelper_mem: the name of the dataset, enclosed in quotes
    Writes global macro variable:
        nobsHelper_obs: the number of observations in the dataset 

    Take care nobsHelper exists before calling this macro, or it will be ost
**;
%macro nobsHelper();
    ** Make sure nobsHelper_obs is a global macro variable**;
    %global nobsHelper_obs;

    proc sql noprint;
        select nobs
        into :nobsHelper_obs
        from sashelp.vtable
        where libname = %UPCASE(&nobsHelper_lib)
          and memname = %UPCASE(&nobsHelper_mem);
    quit;
    %* uncomment these put statements to debug **;
    %*put NOTE: inside nobsHelper, the following macro variables are known;
    %*put _user_;
%mend;

第二步:写一个辅助函数

**Functions need to be stored in a compilation library;
options cmplib=sasuser.funcs;

** function nobsHelper, retrieves the number of observations in a dataset
    Writes global macro variables:
        nobsHelper_lib: the library in which the dataset resides, enclosed in quotes
        nobsHelper_mem: the name of the dataset, enclosed in quotes
    Calls the macro nobsHelper

    Uses macro variable:
        nobsHelper_obs: the number of observations in the dataset 
**;
proc fcmp outlib=sasuser.funcs.trial;
    ** Define the function and specity it should be called with two character vriables **;
    function nobsHelper(nobsHelper_lib $, nobsHelper_mem $);
        ** Call the macro and pass the variables as global macro variables 
        ** The macro variables will be magically qouted **;
        rc = run_macro('nobsHelper', nobsHelper_lib, nobsHelper_mem);
        if rc then put 'ERROR: calling nobsHelper gave ' rc=;

        ** Retreive the result and pass it on **;
        return (symget('nobsHelper_obs'));
    endsub;
quit;

第 3 步:编写一个方便的宏来使用 helpers

** macro nobs retrieves the number of observations in a dataset
    Parameters:
        library_name: the library in which the dataset resides
        member_name: the name of the dataset
    Inserts in your code:
        the number of observations in the dataset 
    Use as a function
**;
%macro nobs(library_name, member_name);
    %sysfunc(nobsHelper(&library_name, &member_name));

    %* Uncomment this to debug **;
    %*put _user_;
%mend;

最后使用它

%let num_carrs = %nobs(sasHelp, cars);
%put There are &num_carrs cars in sasHelp.Cars;

Data aboutClass;
    libname = 'SASHELP';
    memname = 'CLASS';
    numerOfStudents = %nobs(sasHelp, class);
run;

我知道这很复杂,但至少所有的书呆子工作都完成了。 您可以在老板接受的时间内复制、粘贴和修改此内容。 ;

SAS 宏插入代码。 它永远不会返回值,但在某些情况下您可以模仿函数,通常您需要解决类似的问题

%nobs(work, patients, toReturn=num_of_observations )

** 为了帮助您了解会发生什么,我建议将宏插入的代码打印到您的日志中:

options mprint;

我们把宏变量的名字传给宏来填,我觉得最实用

  • 不需要我的宏的用户在库和成员名称周围加上引号
  • 使变量的名称成为一个命名的宏变量,这样我们就可以给它一个默认值;

    %macro nobs(library_name, table_name, toReturn=nobs);

确保要返回的变量存在

  • 如果它存在,则在此宏之外是已知的。
  • 否则如果我们在这里创建它,默认情况下它会是本地的,当我们离开宏时会丢失;

     %if not %symexist(&toReturn.) %then %global &toReturn.;

在 SQL 中,我

  • 使用 SASHELP.VTABLE,SAS 为其元数据提供的视图
  • 添加我在宏调用中省略的引号(“”,而不是 '':宏变量不会在单个引用中替换)
  • 使用宏 %upcase 函数而不是 SAS upcase 函数,因为它有时会提高性能;

     proc sql noprint; select nobs into :&toReturn. from sashelp.vtable where libname = %UPCASE("&library_name.") and memname = %UPCASE("&table_name."); quit;

    %修补;

注意如果你在宏中调用宏,运行这段代码并阅读日志以了解原因;

%macro test_nobs();
    %nobs(sashelp, class); ** will return the results in nobs **;

    %nobs(sashelp, shoes, toReturn=num_of_shoes);

    %let num_of_cars = ;
    %nobs(sashelp, cars, toReturn=num_of_cars);

    %put NOTE: inside test_nobs, the following macro variables are known;
    %put _user_;
%mend;

%test_nobs;
%put NOTE: outside test_nobs, the following macro variables are known;
%put _user_;

你不能从函数风格的宏中“返回”一个值,除非你只使用宏语句来编写它。 Quentin 的链接提供了如何执行此操作的示例。

例如,您不能像这样使用您的宏,因为 proc sql 不能在%put语句的中间执行(这可以通过其他更复杂的解决方法,例如dosubl ,但不是您编写的方式)。

%put %nobs(mylib,mydata);

在不进行重大更改的情况下,您可以做的最好的事情是创建一个全局宏变量并在后续语句中使用它。

要创建原始宏的局部宏变量,您必须首先通过宏定义中的%local语句声明它。

我知道我对这个讨论已经很晚了,但是自从我遇到这个问题后就想到了评论。 这是我认为的另一种方法:

%macro get_something_back(input1, input2, output);
  &output = &input1 + &input2;
%mend;

data _test_;
  invar1 = 1; invar2 = 2;
  %get_something_back(invar1, invar2, outvar);
end;

这也将在数据步骤之外工作。


%global sum;

%macro get_something_back(input1, input2, outvar);
  %let &outvar = &sysevalf(&input1 + &input2);
%mend;

%get_something(1, 2, sum);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM