SAS - 如何从SAS宏返回值?

时间:2015-11-20 01:33:03

标签: sas

我想从我创建的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宏变量对于该宏是本地的而不是全局的。我怎么能这样做?

5 个答案:

答案 0 :(得分:10)

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

  

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

我将以一种重要的方式与Dirk在这里狡辩。他说:

  
    

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

  

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

然而,如果宏只包含除该值之外的宏语句,则它只能返回单个值。意思是,每一行都必须以%开头。将返回任何不以%开头的内容(也可能会返回一些以%开头的内容。)

所以重要的问题是,如何从宏返回

在某些情况下,与此类似,仅使用 宏代码完全可以实现。事实上,在许多情况下,这在技术上是可行的 - 尽管在许多情况下,它比你应该做的更多。

Jack Hamilton的链接paper包含了一个适合的例子。他驳回了这个例子,但这很大程度上是因为他的论文是关于在NOBS错误的情况下计算观察 - 使用WHERE子句,或者在没有修改数据集的某些其他情况下NOBS元数据正在更新。

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

返回值的宏必须具有完全一个语句,该语句不是宏语法语句,或者宏语法语句,它将值返回到处理中流。 %sysfunc是一个声明的例子。 %let%put%if等内容是不会返回任何内容的语法语句(单独使用);所以你可以拥有你想要的那么多。

你也有一个语句在处理流中放置一个值:否则你根本不会从宏中获取任何东西。

以下是第3页末尾的杰克宏的精简版,简化为删除他显示的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函数来找出任何特定数据集中有多少行。

答案 1 :(得分:8)

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

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

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

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

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

现在我们已准备好实际解决方案

第1步:编写辅助宏

  • 没有参数,
  • 使用一些全局宏变量
  • "返回"它的结果是一个全局宏变量

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

** 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;

第2步:编写辅助函数;

**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步:编写一个方便的宏来使用帮助;

** 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;

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

答案 2 :(得分:3)

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

%nobs(work, patients, toReturn=num_of_observations )

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

options mprint;

我们传递宏变量的名称以填充宏,我发现它最实用

  • 不要求我的宏的用户在libary和成员名称周围加上引号
  • 将变量的名称命名为宏变量,因此我们可以给它一个默认值;

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

确保要返回的变量

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

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

在SQL 中,我

  • 使用SASHELP.VTABLE,SAS在其元数据
  • 上提供的视图
  • 添加我在宏调用中省略的引号(“”,而不是'':宏变量不会在单个qoutes中替换)
  • 使用宏%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_;

答案 3 :(得分:2)

你无法返回'函数式宏的值,除非您仅使用宏语句编写它。 Quentin的链接提供了如何执行此操作的示例。

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

%put %nobs(mylib,mydata);

如果没有重大更改,您可以做的最好的事情是创建一个全局宏变量并在后续语句中使用它。

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

答案 4 :(得分:0)

我知道我这次讨论很晚,但是自从我遇到这个问题后就想到发表评论。我认为这是另一种方式:

%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);