【问题标题】:A macro function to produce a macro variable from a data variable从数据变量生成宏变量的宏函数
【发布时间】:2015-02-16 07:29:03
【问题描述】:
data sample;
    input x $;
    datalines;
one
two
three
;

%macro variable_to_macvar(variable=, dataset=);
    proc sql noprint;
        select &variable into : outlist separated by ' ' 
        from &dataset;
    quit;
&outlist
%mend variable_to_macvar;

%put %variable_to_macvar(variable=x, dataset=sample);

预期输出:one two three相反,我得到了一个错误。为什么?这可以修复吗?

我已经成功地创建了其他形式非常相似的宏,其中该函数使用宏末尾的 &macrovariable(不带分号)“返回”一个值。例如,下面是一个类似的函数:

%macro zcat(first=5, last=15, prefix=freq); 
    %let x=;   
    %do i = &first %to &last;
        %let x=&x &prefix.&i;
    %end;
    &x
%mend zcat;
%put %zcat();

【问题讨论】:

    标签: sas sas-macro


    【解决方案1】:

    您不能以您在此处尝试的方式执行涉及运行过程或数据步骤的宏。您需要使用 %sysfunc(dosubl(proc sql...)) 之类的东西才能使其正常工作(假设您有 SAS 9.3+ - 请参阅上面乔的回答)。否则,您不能在函数式宏中使用 proc sql。

    更多关于dosubl的细节: http://support.sas.com/documentation/cdl/en/lefunctionsref/67398/HTML/default/viewer.htm#p09dcftd1xxg1kn1brnjyc0q93yk.htm

    这有点繁琐,但如果您真的想在早期版本的 SAS 中将其作为函数式宏工作,您可以使用 openfetchobsgetvarc 函数来构建它.

    更新:这是一个示例(使用call set 而不是getvarc,因为事实证明这更简单),以防有人需要在 SAS 9.2 或更早版本中执行此操作。

    %macro variable_to_macvar(var,ds);
        %local rc dsid i;
        %let &var =;
        %global outlist;
        %let outlist=;
        %let dsid = %sysfunc(open(&ds,i));
        %syscall set(dsid);
        %let rc = 0;
        %let i = 0;
        %do %while(&rc = 0);
            %let i = %eval(&i + 1);
            %let rc = %sysfunc(fetchobs(&dsid,&i));
            %if &rc = 0 %then %let outlist = &outlist &&&var;
        %end;
        %let rc = %sysfunc(close(&dsid));
        &outlist
    %mend;
    
    %put %variable_to_macvar(var=x, ds=sample);
    

    现在适用于视图以及普通数据集。

    【讨论】:

    • 我现在卡在 9.3 上
    • 在宏内运行 proc 似乎可行;只有我传递变量的部分失败。具体来说,在宏末尾附近包含%put &outlist; 会产生所需的结果。
    • 我对您的“繁琐”解决方案感兴趣,如果它不是非常复杂的话。我不知道你会怎么做,所以更多细节会很有趣。
    • 我会在几分钟内得到一些东西。
    • 很好,我喜欢看看如何做到这一点。
    【解决方案2】:

    最后修改你的代码

    %global outlist;
    %variable_to_macvar(variable=x, dataset=sample);
    %put &outlist;
    

    %put 只想解析宏变量或单个值。它不能调用过程。所以调用你的宏然后打印结果。

    另外,从宏定义中删除&outlist。抱歉,我最初错过了。

    编辑:替代。

    将您的宏定义更改为

    %macro variable_to_macvar(variable=, dataset=);
        proc sql noprint;
            select &variable into : outlist separated by ' ' 
            from &dataset;
        quit;
    %put &outlist
    %mend variable_to_macvar;
    

    只需在宏内执行 %put。

    %variable_to_macvar(variable=x, dataset=sample);
    

    会将字符串打印到日志中。

    【讨论】:

    • 很有趣,但我真的希望variable_to_macvar 在一行代码中作为基本函数运行,就像我的后一个示例一样。
    • 如果我的目标只是“放入”日志,这将是主要的解决方案。但我应该在我最初的问题中更加明确:在一个典型的用例中,我会将输出分配给一个宏变量,比如%let xmacvar = %variable_to_macvar(variable=x, dataset=sample);
    【解决方案3】:

    DOSUBL 在 9.3 中可用(但实验性)(至少,9.3TS1M2,我有)。这就是你的做法。

    data sample;
        input x $;
        datalines;
    one
    two
    three
    ;
    
    %macro variable_to_macvar(variable=, dataset=);
       %let rc=%sysfunc(dosubl(%str(
        proc sql noprint;
            select &variable into : outlist separated by ' ' 
            from &dataset;
        quit;
        )));
    &outlist
    %mend variable_to_macvar;
    
    %put %variable_to_macvar(variable=x, dataset=sample);;
    

    如果您不能使用 DOSUBL,或者想避免实验性的事情,您可以使用 PROC FCMP 而不是宏来执行此操作。如果您喜欢编写函数,PROC FCMP 可能适合您:实际上能够编写函数,而不必处理宏语言的烦恼。

    【讨论】:

      【解决方案4】:

      我们有一个实用程序宏,它可能是我们最常用的代码之一,可以为我们执行此操作。它类似于@user667489 提供的代码,但包含一些不错的功能,包括错误捕获、允许字符和数字变量、允许您指定分隔符、引号、引号字符、数据集过滤器等......

      我们只是将这个宏放在我们的自动调用库中,以便我们所有的程序都可以使用它。运行宏的一些示例:


      示例 1 - 默认行为:

      %put %variable_to_macvar(var=x, ds=samplex);
      

      结果 1:

      one,two,three
      

      由于默认分隔符是逗号,所以输出不是很理想,不过这很容易更改...


      示例 2 - 指定使用空格字符作为分隔符:

      %put %ds2list(iDs=samplex, iField=x, iDelimiter=%str( ));
      

      结果 2:

      one two three
      

      示例 3 - 引用和示例用法

      data names;
        input name $;
        datalines;
      John
      Jim
      Frankie
      ;
      run;
      
      %put %ds2list(iDs=names, iField=name, iQuote=1);
      
      proc sql noprint;
        create table xx as 
        select *
        from sashelp.class
        where name in (%ds2list(iDs=names, iField=name, iQuote=1))
        ;
      quit;
      

      结果 3:

      以下内容打印到日志中:

      'John','Jim','Frankie'
      

      请注意,我们甚至不需要将结果保存到宏变量中即可在 SQL 语句中使用它!甜甜的!这同样适用于 SQL 直通查询,以及您可以抛出的任何其他数据步骤或 proc 语句。在上面的示例中,返回单行,因为 'John' 是找到的唯一匹配项...


      无论如何,这就是我们的解决方案……已经使用了 10 年以上,对我来说效果很好。这是宏:

      /***************************************************************************
      **  PROGRAM: MACRO.DS2LIST.SAS
      **
      **  UTILITY PROGRAM THAT DETECTS RETURNS A LIST OF FIELD VALUES FROM A 
      **  DATASET IN DELIMITED FORMAT.
      **
      **  PARAMETERS:
      **  iDs       : THE LIBNAME.DATASET NAME THAT YOU WANT TO CHECK.
      **  iField    : THE FIELD THAT CONTAINS THE VALUES YOU WANT RETURNED IN A 
      **              DELIMITED FORMAT.
      **  iDelimiter: DEFAULT IS A COMMA. THE DELIMITER TO USE FOR THE RETURNED LIST.
      **  iDsOptions: ANY STANDARD DATASET OPTIONS THAT YOU WOULD LIKE TO APPLY SUCH 
      **              AS A WHERE STATEMENT.
      **  iQuote    : (0=NO,1=YES). DEFAULT=0/NO. DETERMINES WHETHER THE RETURNED 
      **              LIST IS QUOTED OR NOT.
      **  iQuoteChar: (SINGLE,DOUBLE) DEFAULT=SINGLE. SPECIFIES WHETHER SINGLE
      **              OR DOUBLE QUOTES ARE USED WHEN QUOTING THE RETURNED LIST
      **
      *****************************************************************************
      ** VERSION:
      **  1.8 MODIFIED: 11-OCT-2010  BY: KN
      **      ALLOW BLANK CHARACTER VALUES AND ALSO REMOVED TRAILING
      **      ALLOW PARENTHESES IN CHARACTER VALUES
      *****************************************************************************/
      
      %macro ds2list(iDs=, iField=, iDsOptions=, iDelimiter=%str(,), iQuote=0, iQuoteChar=single);
        %local dsid pos rc result cnt quotechar;
      
        %let result=;
        %let cnt=0;
      
        %if &iQuote %then %do;
          %if "%upcase(&iQuoteChar)" eq "DOUBLE" %then %do;
            %let quotechar = %nrstr(%");
          %end;
          %else %if "%upcase(&iQuoteChar)" eq "SINGLE" %then %do;
            %let quotechar = %nrstr(%');
          %end;
          %else %do;
            %let quotechar = %nrstr(%");
            %put WARNING: MACRO.DS2LIST.SAS: PARAMETER IQUOTECHAR INCORRECT. DEFAULTED TO DOUBLE;
          %end;
        %end;
        %else %do;
          %let quotechar = ;
        %end;
      
        /*
        ** ENSURE ALL THE REQUIRED PARAMETERS WERE PASSED IN.
        */
        %if "&iDs" ne "" and "&iField" ne "" %then %do;
      
          %let dsid=%sysfunc(open(&iDs(&iDsOptions),i));
          %if &dsid %then %do;
      
            %let pos=%sysfunc(varnum(&dsid,&iField));
            %if &pos %then %do;
      
              %let rc=%sysfunc(fetch(&dsid));
              %do %while (&rc eq 0);
      
                %if "%sysfunc(vartype(&dsid,&pos))" = "C" %then %do;
                  %let value = %qsysfunc(getvarc(&dsid,&pos));
                  %if "%trim(&value)" ne "" %then %do;
                    %let value = %qsysfunc(cats(%nrstr(&value)));
                  %end;
                %end;
                %else %do;
                  %let value = %sysfunc(getvarn(&dsid,&pos));
                %end;
      
                /* WHITESPACE/CARRIAGE RETURNS REMOVED IN THE BELOW LINE */
                /* TO ENSURE NO WHITESPACE IS RETURNED IN THE OUTPUT.    */
                %if &cnt ne 0 %then %do;%unquote(&iDelimiter)%end;%unquote(&quotechar&value&quotechar.)
      
                %let cnt = %eval(&cnt + 1);
                %let rc  = %sysfunc(fetch(&dsid));
              %end;
      
              %if &rc ne -1 %then %do;
                %put WARNING: MACRO.DS2LIST.SAS: %sysfunc(sysmsg());
              %end;
      
            %end;
            %else %do;
              %put ERROR: MACRO.DS2LIST.SAS: FIELD &iField NOT FOUND IN DATASET %upcase(&iDs).;
            %end;
          %end;
          %else %do;
            %put ERROR: MACRO.DS2LIST.SAS: DATASET %upcase(&iDs) COULD NOT BE OPENED.;
          %end;
      
          %let rc=%sysfunc(close(&dsid));
      
        %end;
        %else %do;
          %put ERROR: MACRO.DS2LIST.SAS: YOU MUST SPECIFY BOTH THE IDS AND IFIELD PARAMETERS TO CALL THIS MACRO.;
        %end;
      
      %mend;
      

      【讨论】:

      • 我只是在查看这段代码,这是@Joe 在提到“处理宏语言的烦恼”时所说的一个很好的例子。所有这些对qsysfuncnrstrunquote 的电话都让我畏缩……
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-06-08
      • 1970-01-01
      • 1970-01-01
      • 2022-11-30
      • 2013-10-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多