SAS:查找函数/子例程内的数据/返回数组

SAS:查找函数/子例程内的数据/返回数组,sas,sas-macro,Sas,Sas Macro,假设我喜欢执行以下操作(使用示例性变量名以提高可读性): 在parameter中获取一个参数,并将其与数据集中的变量MyVar1相匹配MyData 返回筛选观察值的变量MyVar2的所有值 从子例程/函数 我可以在proc sql/datastep中使用 这就是我目前得到的结果(显然不起作用): 简而言之: 数据集中的数据可以从函数/子例程内部访问吗 函数/子例程能否返回数组 谢谢你的帮助 您最好使用宏 %macro subset(inParameter, indata, outdata);

假设我喜欢执行以下操作(使用示例性变量名以提高可读性):

  • 在parameter中获取一个参数
    ,并将其与数据集中的变量
    MyVar1
    相匹配
    MyData

  • 返回筛选观察值的变量
    MyVar2
    的所有值

  • 从子例程/函数

  • 我可以在proc sql/datastep中使用

  • 这就是我目前得到的结果(显然不起作用):

    简而言之:

    • 数据集中的数据可以从函数/子例程内部访问吗
    • 函数/子例程能否返回数组

    谢谢你的帮助

    您最好使用宏

    %macro subset(inParameter, indata, outdata);
    
    proc sql noprint;
    create table &outdata as
    select * from &indata
       where myVar2 in (select distinct myVar2 from myData where myVar1 = "&inParameter);
    quit;
    %mend;
    
    %subst(H20,Somedata,MyOutput);
    

    不确定函数是否能与
    运算符中的
    一起工作。您可能需要使用宏包装函数调用以生成正确的语法。在这种情况下,为什么不先做一个宏呢

    下面是从数据集中的变量中提取值的通用宏

    %macro varlist
    /*----------------------------------------------------------------------
    Generate list of values from dataset
    ----------------------------------------------------------------------*/
    (dataset     /* Input dataset */
    ,variable    /* Variable Name */
    ,quote=1     /* Add quotes around values? 1=Single 2=Double */
    ,comma=1     /* Add comma between values? */
    ,paren=1     /* Add parentheses around results? */
    );
    %local did sep &variable ;
    %if &paren=1 %then (;
    %let did=%sysfunc(open(&dataset));
    %syscall set(did);
    %do %while(0=%sysfunc(fetch(&did)));
      %let &variable=%qsysfunc(trim(%superq(&variable)));
      %if &quote=1 %then &sep.%sysfunc(quote(&&&variable,%str(%')));
      %else %if &quote=2 %then &sep.%sysfunc(quote(&&&variable));
      %else &sep.&&&variable;
      %if &comma=1 %then %let sep=,;
    %end;
    %let did=%sysfunc(close(&did));
    %if &paren=1 %then );
    %mend varlist;
    
    电话示例:

    %put %varlist(sashelp.class,name);
    %put %varlist(sashelp.class(where=(sex='M')),age,quote=0,comma=0);
    
    因此,在您的情况下,您可以这样使用它:

    data MyOutput;
      set Somedata;
      where MyVar2 in %varlist(Mydata(where=(MyVar1="H20")),MyVar2) ;
    run;
    

    我们创建了一个名为
    %ds2list()
    的实用程序宏,它将执行所需的过程。它不使用array语句,但实现了相同的结果

    宏只是以列表格式从数据集中返回值。下面是一个调用它的示例:

    %put %ds2list(iDs=sashelp.class, iField=name, iQuote=1);
    
    这将返回:

    'Alfred','Alice','Barbara','Carol','Henry','James','Jane','Janet','Jeffrey','John','Joyce','Judy','Louise','Mary','Philip','Robert','Ronald','Thomas','William'
    
    %ds2list()
    的默认行为是用逗号分隔返回的值,但它非常灵活。您可以将分隔符更改为您选择的值(或无分隔符),可以打开或关闭引号,或将其从单引号更改为双引号,还可以提供通常在
    set
    语句(如
    where=()
    语句)上使用的任何数据集选项

    此外,因为宏是纯宏代码,所以您可以在SAS的任何地方使用它。在您喜欢的任何进程/数据/宏中。当我们有一个想要返回的ID的大列表时,我们广泛地使用它来调用ODBC passthrough

    下面是一个如何使用它的示例。首先创建一个表,其中包含要与列表值进行比较的值:

    data keep;
      input name $;
      datalines;
    Alfred
    Carol
    Janet
    run;
    
    迭代要对照列表检查的值:

    data want;
      set keep;
      if name in (%ds2list(iDs=sashelp.class, iField=name, iQuote=1, iDsOptions=where=(sex='F'))) then do;
        output;
      end;
    run;
    
    返回:

    Obs    name
    ===    =====
     1     Carol
     2     Janet
    
    您可以看到Alfred被排除在结果之外,因为他被
    where=()
    子句过滤掉了

    这是宏,我建议将其放入宏自动调用库中:

    /***************************************************************************
    **  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
    **
    *****************************************************************************/
    
    %macro ds2list(iDs=, iField=, iDsOptions=, iDelimiter=%str(,), iQuote=0, iQuoteChar=single);
      %local dsid pos rc result cnt quotechar value;
    
      %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 = %qtrim(&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;
    

    SAS中的数组是一个奇特的变量列表。函数返回一个值。调用例程可以改变一个或多个现有变量的值。好吧,我将在
    函数
    上使用
    子例程
    ,因为数组总是被视为多个变量?我认为您需要将函数包装在宏中,以便在
    操作符中为
    生成有效的SAS语法。在哪种情况下,您仍然需要生成函数?函数不知道其操作上下文。因此,如果您的想法是“函数应该知道它是从数据步骤调用的,我希望函数知道活动集或PDV中可用的变量”,这是一个死胡同。@Richard的想法是从另一个硬编码数据集中返回一些值(我想这不是您所说的“操作上下文”)以一种允许在数据步骤中使用返回值的方式。不完全是我想要的,但这很可能是因为我的问题措词不当。这将复制上一个数据步骤的结果。然而,我的问题是使用不同的过滤器创建这个特定的数据集。比如说,我可以在datastep中使用
    调用execute(proc-sql)
    。我想提供一种可读的方式,在任何数据步骤中以所描述的方式进行过滤,而不考虑我执行的任何其他操作。这真是太棒了。这是一段非常棒的代码,它确实做到了我需要它做的事情!遗憾的是,论坛不允许我将你的优秀代码和相似代码都标记为答案。虽然我最终使用了这个方法的一个变体,但我认为其他用户可能更容易使用Tom的不太广泛的方法。@MartinDreher是的,有选择总是很好的。我的和Tom的唯一真正区别是我的做了一些错误检查,参数检查,并打印出调试信息。我的也允许选择分隔符。否则它们基本上是一样的。
    /***************************************************************************
    **  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
    **
    *****************************************************************************/
    
    %macro ds2list(iDs=, iField=, iDsOptions=, iDelimiter=%str(,), iQuote=0, iQuoteChar=single);
      %local dsid pos rc result cnt quotechar value;
    
      %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 = %qtrim(&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;