SAS-如何从SAS宏返回值?

SAS-如何从SAS宏返回值?,sas,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_n

我想从我创建的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
宏变量是该宏的局部变量,而不是全局变量。如何才能做到这一点?

除非仅使用宏语句编写,否则无法从函数样式宏“返回”值。昆汀的链接提供了一个如何做到这一点的例子

例如,您不能使用这样的宏,因为PROC SQL不能在<代码> %PUT/<代码>语句的中间执行(这可能是其他更复杂的处理方法,例如:代码> DOUBUL,但不是您编写的方法)。 在没有重大更改的情况下,最好创建一个全局宏变量,并在后续语句中使用它


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

SAS宏插入代码。它永远不会返回值,尽管在某些情况下可以模拟函数,通常需要类似的解决方法

%nobs(work, patients, toReturn=num_of_observations )
**为了帮助您理解发生了什么,我建议打印宏在日志中插入的代码:

options mprint;
我们将要填充的宏变量的名称传递给宏,我发现最实用的方法是

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

    %宏nobs(库名称、表名称、toReturn=nobs)

确保要返回的变量存在

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

    %if not %symexist(&toReturn.) %then %global &toReturn.;
    
在SQL中,I

  • 使用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_;

编写宏这样的函数有什么问题?

i、 e.可以用作
%let myVar=%myMacro(myArgument)

  • 您可以使用用户编写的宏,就像它是一个函数一样,如果您所做的只是
    • 调用一些类似宏函数的
      %doSomething(with someting)
    • 使用
      %let someVar=
      语句为宏变量赋值
    • “返回”您的结果,通常在
      %mend
  • 一旦在宏中包含
    proc
    data
    步骤,这将不再有效
  • 幸运的是,%sysFunc()起到了解救作用,因此我们可以使用任何数据步函数
  • 这包括低级功能,如
    open
    fetch
    close
    ,它们甚至可以访问您的数据
  • 书呆子们可以用它做很多事情,但即使你是书呆子,你的老板也很少给你时间这么做
我们如何解决这个问题?,即我应该使用哪些构建块来解决这个问题

  • proc fcmp
    允许将一些数据步骤语句打包到子例程或函数中
  • 此函数用于数据步骤,可在
    %sysfunc()
  • 在此函数中,您可以调用
    run\u 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;
** 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;
步骤3:编写一个方便的宏来使用帮助程序

**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;
** 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;
最后使用它

**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;
** 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宏插入代码。它永远不会返回值,尽管在某些情况下可以模拟函数

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

但是,如果宏除返回一个值外还包含宏语句,则该宏只能返回该值。也就是说,每一行都必须以
%
开头。任何不以
%
开头的内容都将被返回(一些以
%
开头的内容也可能被返回)

因此,重要的问题是,如何仅返回宏中的值


在某些情况下,如本例,完全可以只使用宏代码。事实上,在许多情况下,这在技术上是可能的——尽管在许多情况下,这比你应该做的工作要多

Jack Hamilton的链接中包含了一个适用于此的示例。他驳斥了这个例子

%global sum;

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

%get_something(1, 2, sum);