如何在 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("e_year. = &start_year.,%SYSFUNC(PUTN(&start_month.,z2.)),1,.)) %TO %SYSFUNC(IFN("e_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("e_year. = &start_year.,&start_month.,1,.)) %TO %SYSFUNC(IFN("e_year. = &end_year.,&end_month.,12,.));
dataset_"e_year."e_month.
%END;
%END;
;
RUN;
%MEND;
%get_table(&start_date.,&end_date.);
您可以使用 putn
和 z2.
格式执行此操作。
%DO quote_year = &start_year. %TO &end_year.;
%DO quote_month = %SYSFUNC(IFN("e_year. = &start_year.,&start_month.,1,.)) %TO %SYSFUNC(IFN("e_year. = &end_year.,&end_month.,12,.));
dataset_"e_year.%sysfunc(putn("e_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
计算日期值循环的界限。在循环内:
- 根据指定的
lib
、prefix
和期望的日期值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;
所以我在特定库中有一系列数据集。这些数据集以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("e_year. = &start_year.,%SYSFUNC(PUTN(&start_month.,z2.)),1,.)) %TO %SYSFUNC(IFN("e_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("e_year. = &start_year.,&start_month.,1,.)) %TO %SYSFUNC(IFN("e_year. = &end_year.,&end_month.,12,.));
dataset_"e_year."e_month.
%END;
%END;
;
RUN;
%MEND;
%get_table(&start_date.,&end_date.);
您可以使用 putn
和 z2.
格式执行此操作。
%DO quote_year = &start_year. %TO &end_year.;
%DO quote_month = %SYSFUNC(IFN("e_year. = &start_year.,&start_month.,1,.)) %TO %SYSFUNC(IFN("e_year. = &end_year.,&end_month.,12,.));
dataset_"e_year.%sysfunc(putn("e_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
计算日期值循环的界限。在循环内:- 根据指定的
lib
、prefix
和期望的日期值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;