首先,产生测试数据集:
data yyx.retain1;
input id txn_cde $ cns txn_dte $;
cards;
10 101 10 20070101
10 101 20 20080402
10 201 30 20050203
20 101 40 20040105
20 201 50 20040105
20 301 60 20070806
20 201 70 20050607
30 301 80 20070501
30 401 90 20070306
;
run;
需求如下:
1)按照每一个ID,汇总CNS的值。
2)按照每一个ID,汇总ID的记录数。
3)按照每一个ID,如果TXN_CDE变量取101和201两个值,则累加计算一次。
4)按照每一个ID,计算TXN_DTE的最小值。
5)每个ID按照上述条件输出最后一条记录。
程序用SQL过程实现:
proc sql;
create table sum as
select
id
,sum(cns) as cns
,count(*) as count
,sum(case when txn_cde in("101" "201") then 1 else 0 end) as cnt_condi
,min(txn_dte) as min_txn_dte
from yyx.retain1
group by 1
;
quit;
但是呢,在实际中很少应用SQL,主要原因是SAS的SQL过程比DATA步效率要低得多,尤其是遇到大数据量或汇总变量比较多的时候更是如此。
程序用DATA步来实现:
proc sort data=yyx.retain1;by id txn_dte;run;
data test1;
set yyx.retain1;
by id txn_dte;
retain min_dte sum_cns cnt cnt_condition;
if first.id then do;
min_dte=txn_dte;
sum_cns=0;
cnt=0;
cnt_condition=0;
end;
min_dte=min(min_dte,txn_dte);
sum_cns+cns;
cnt+1;
cnt_condition+(txn_cde in("101" "201"));
if last.id;
run;
程序解读:
首先说一个不得不说的解释:
first.variable指的是按照variable排序后的每一组variable中的第一个variable,例如按照id排序,那么first.id就是排序后每一组id的第一个id。last同理,为每一组id的最后一个id。
由于DATA步需要用到FIRST和LAST变量,因此必须在DATA步之前用SORT过程对FIRST变量进行排序,这一点非常重要。对接下来的主体程序DATA步整体架构共分为5块:
【1】SET/BY模块:SET语句是读数据集,当然必不可少,一定要注意BY语句不能丢,BY后面的变量就是刚才SORT过程中的排序变量。
【2】RETAIN模块:该模块对需要RETAIN的所有变量做RETAIN声明。
【3】FIRST模块:该模块对RETAIN变量按照每一个FIRST.ID做初始化。
【4】主体模块:该模块介于FIRST和LAST之间,主要是完成对需求的运算。
【5】LAST模块:该模块主要完成最后的输出,有时候也需要继续在该模块对需求进行进一步的数据处理。
程序运行流程如下:
1)首先进行程序编译,并生成一条PDV,对除RETAIN变量之外的其他所有变量置为缺失。
2)第一次执行SET/BY模块:按照BY变量的排序读入第一条观测。
3)第一次执行IF FIRST模块:
首先我要讲一下此处的IF条件是如何判断的:
程序中的if first.id then do;这句话我刚开始考虑的是if判断的是first.id的值存不存在,然后转念一想,first.id任何时候都是存在的。这不就成了永真了吗?后来突然灵机一动,想到了原理。
原来,这条语句这时候判断的依据是读取到PDV的观测值,就拿上边的if first.id then do;语句来说,这条语句的意思是,如果现在要处理的语句是通过by分组之后的一组数据之中的第一条观测,那么结果就是真,如果不是,那判断结果就是假。
由于需求条件较多,由此赋值语句也较多,故在此用DO组语句套住这些赋值语句,但是这里的DO组语句不是循环语句。在DO组语句里面,根据需求条件的不同,对min_dte需要赋交易时间(txn_dte)作为其初始值,而其余RETAIN变量赋初始值零。
4)第一次执行主体模块:min_dte=min(min_dte,txn_dte),该赋值语句比较已经被RETAIN的变量min_dte(对应FIRST.ID中的txn_dte值)和第一条观测txn_dte值,并取两者之间最小者,重新赋值给变量min_dte。sum_cns+cns,该语句是累加语句,表示把变量CNS值累加到变量sum_cns上去,相当于:sum_cns=sum_cns+cns。cnt+1含义和sum_cns类似,只是其作用是做计数器累加,计数器累加在程序中经常用到。cnt_condition+(txn_cde in("101" "102")),括号里面的txn_cde in("101" "102")是个表达式判断语句,即如果条件成立,则返回值1,否则返回0,并累加到cnt_condition上去。
5)未执行IF LAST模块:没有执行的原因是此时PDV刚刚读入第一条观测,而对ID变量组成的BY组而言,第一个BY组对应的ID=10 应该有三条观测。所以ID=10 对应的LAST应该是第三条观测。
6)未执行RUN语句:这是由IF语句特征决定的,如果程序判断IF语句为假(不是IF/ELSE语句),将会忽略IF语句下面所有的语句,包括执行语句和声明语句等,程序会立刻跳到DATA步开头,继续读入下面的观测。可以看出,在有IF语句存在的情况下,RUN语句执行的次数将非常少,大家知道,只有RUN语句被执行,PDV缓存区数据才能输出。因此只有IF语句为真,程序才能输出观测。
7)由于上述原因,程序将直接跳到DATA步,这时,所有非RETAIN变量将会被重置为缺失值,而RETAIN变量值如下:min_dte=20070101;sum_cns=10;cnt=1;
cnt_condition=1。
8)第二次执行SET/BY模块:不执行IF FIRST模块,第二次执行主体模块,不执行IF LAST模块,最后程序直接跳到DATA步开头,所有非RETAIN变量又被重置为缺失值,而RETAIN变量值如下:min_dte=20070101(即20070101=min(20070101,20080402));sum_cns=30;cnt=2;cnt_condition=2。
9)第三次执行SET/BY模块:不执行IF FIRST模块,第三次执行主体模块,执行IF LAST模块,第一次执行RUN语句,输出所有变量值如下:ID=10;txn_cde=101;cns=10;txn_cde=20070101;min_dte=20050203;sum_cns=60;cnt=3;cnt_condition=3。
从第四条观测开始,程序将执行第二和第三个BY组。
最后的输出结果如下: