如何在 SAS 宏循环计数器中用前导零填充数字?

How to pad a number with leading zero in a SAS Macro loop counter?

所以我在特定库中有一系列数据集。这些数据集以DATASET_YYYYMM的格式命名,每个月一个数据集。我正在尝试根据日期范围内的用户输入附加一系列这些数据集。即如果 start_date 是 01NOV2019 而 end_date 是 31JAN2020,我想附加三个数据集:LIBRARY.DATASET_201911、LIBRARY.DATASET_201912 和 LIBRARY.DATASET_202001 .

范围显然是可变的,所以我不能简单地在 set 函数中手动命名数据集。由于我需要遍历日期范围内的年份和月份,我相信宏是执行此操作的最佳方式。我在 SET 语句中使用循环来附加所有数据集。我在下面复制了我的示例代码。它在理论上确实有效。但实际上,只有当我们在 11 月和 12 月循环时。由于数据集名称的格式有两位数的月份,因此对于 Jan-Sept,它将是 01-09。但是,月份函数 returns 1-9,当然会抛出 'File DATASET_NAME does not exist' 错误。 问题是我想不出一种方法让它用前导 0 解释月份,同时又不破坏 loop/macro 另一部分的功能。

我尝试了多种方法将数字格式化为 z2,但都无法正常工作。 即在 quote_month 的 DO 行中包含 PUTN 函数,如下所示,在下面的行中生成数据集名称时会忽略前导零。

%DO quote_month = %SYSFUNC(IFN(&quote_year. = &start_year.,%SYSFUNC(PUTN(&start_month.,z2.)),1,.)) %TO %SYSFUNC(IFN(&quote_year. = &end_year.,%SYSFUNC(PUTN(&end_month.,z2.)),12,.));

下面是示例代码(没有尝试将其重新格式化为 z2)- 它会抛出错误,因为它找不到 'dataset_20201',因为它实际上被称为 'dataset_202001'。名为 dataset_combined_example 的数据集通过手动引用数据集名称来生成所需的代码输出,而这在实践中是无法做到的。有谁知道该怎么做?

DATA _NULL_;
    FORMAT start_date end_date DATE9.;
    start_date = '01NOV2019'd;
    end_date = '31JAN2020'd;
    CALL symput('start_date',start_date);
    CALL symput('end_date',end_date);
RUN;

DATA dataset_201911;
   input name $;
   datalines;
Nov1
Nov2
;
RUN;
DATA dataset_201912;
   input name $;
   datalines;
Dec1
Dec2
;
RUN;
DATA dataset_202001;
   input name $;
   datalines;
Jan1
Jan2
;
RUN;

DATA dataset_combined_example;
    SET dataset_201911 dataset_201912 dataset_202001;
RUN;


%MACRO get_table(start_date, end_date);
    %LET start_year = %SYSFUNC(year(&start_date.));
    %LET end_year = %SYSFUNC(year(&end_date.));
    %LET start_month = %SYSFUNC(month(&start_date.));
    %LET end_month = %SYSFUNC(month(&end_date.));
    DATA dataset_combined;
        SET
            %DO quote_year = &start_year. %TO &end_year.;
                %DO quote_month = %SYSFUNC(IFN(&quote_year. = &start_year.,&start_month.,1,.)) %TO %SYSFUNC(IFN(&quote_year. = &end_year.,&end_month.,12,.));
                    dataset_&quote_year.&quote_month.
                %END;
            %END;
        ;
    RUN;
%MEND;
%get_table(&start_date.,&end_date.);

您可以使用 putnz2. 格式执行此操作。

%DO quote_year = &start_year. %TO &end_year.;
     %DO quote_month = %SYSFUNC(IFN(&quote_year. = &start_year.,&start_month.,1,.)) %TO %SYSFUNC(IFN(&quote_year. = &end_year.,&end_month.,12,.));
            dataset_&quote_year.%sysfunc(putn(&quote_month.,z2.))
     %END;
%END;

您也可以使用元数据表来执行此操作,而不必首先求助于宏循环:

/* A few datasets to combine */
data 
  DATASET_201910
  DATASET_201911
  DATASET_201912
  DATASET_202001
  ;
run;

%let START_DATE = '01dec2019'd;
%let END_DATE = '31jan2020'd; 

proc sql noprint;
  select catx('.', libname, memname) into :DS_LIST separated by ' '
  from dictionary.tables
  where 
    &START_DATE <= 
      case 
        when prxmatch('/DATASET_\d{6}/', memname) 
          then input(scan(memname, -1, '_'), yymmn6.)
        else -99999 
      end 
    <= &END_DATE 
    and libname = 'WORK'
  ;
quit;

data combined_datasets /view=combined_datasets;
  set &DS_LIST; 
run;

where 子句中的 case-when 确保忽略同一库中存在的与预期命名方案不匹配的任何其他数据集。

这种方法的一个主要区别在于,如果您的范围内的预期数据集之一丢失,您将永远不会尝试读取不存在的数据集。

在宏中

  • 使用INTNX计算日期值循环的界限。在循环内:
    • 根据指定的libprefix和期望的日期值format计算候选数据集名称。 <yyyy><mm>按格式输出yymmn6.
    • 使用EXIST检查候选数据集是否存在。
      • 或者,不检查,但确保在组合之前设置 OPTIONS NODSNFERR。该设置将防止在指定不存在的数据集时出错。
    • 将循环索引更新到月底,以便下一次增量将索引带到下个月的月初。

%macro names_by_month(lib=work, prefix=data_, start_date=today(), end_date=today(), format=yymmn6.);

  %local index name;

  %* loop over first-of-the-month date values;
  %do index = %sysfunc(intnx(month, &start_date, 0)) %to %sysfunc(intnx(month, &end_date, 0));

    %* compute month dependent name;
    %let name = &lib..&prefix.%sysfunc(putn(&index,&format));

    %* emit name if it exists;
    %if %sysfunc(exist(&name)) or %sysfunc(exist(&name,VIEW)) %then %str(&name);

    %* prepare index for loop +1 increment so it goes to start of next month;
    %let index = %sysfunc(intnx(month, &index, 0, E));
  %end;  

%mend;

* example usage:

data combined_imports(label="nov2019 to jan2020");
  set 
    %names_by_month(
      prefix=import_,
      start_date='01NOV2019'd,
      end_date = '31JAN2020'd
    )
  ;
run;

您可以使用 Z 格式生成带有前导零的字符串。

但是如果您使用 SAS 日期函数和格式来生成 YYYYMM 字符串,您的问题就会容易得多。只需使用正常的迭代 %DO 循环将月份偏移量从零循环到两个日期之间的月数。

%macro get_table(start_date, end_date);
%local offset dsname ;
data dataset_combined;
  set
%do offset=0 %to %sysfunc(intck(month,&start_date,&end_date));
  %let dsname=dataset_%sysfunc(intnx(month,&start_date,&offset),yymmn6);
  &dsname.
%end;
  ;
run;
%mend get_table;

结果:

445   options mprint;
446   %get_table(start_date='01NOV2019'd,end_date='31JAN2020'd);
MPRINT(GET_TABLE):   data dataset_combined;
MPRINT(GET_TABLE):   set dataset_201911 dataset_201912 dataset_202001 ;
MPRINT(GET_TABLE):   run;