我正在尝试计算当前交易过去5分钟内发生的交易数量。
CALL_DAY TRANS_TIME STORE_NUM TERMINAL CUSTOMER_NUMBER
20130201 10:46:04 1111 1 1
20130202 17:09:19 1111 2 2
20130202 17:10:30 2222 3 3
20130202 17:11:35 2222 3 3
20130202 17:13:26 2222 3 4
以上是发生的所有单独交易。我试图查找特定日期,特定商店编号,过去5分钟内特定终端上发生的交易次数,并为每行创建一个列,列出每个交易的编号。
到目前为止,我已将日期和时间转换为日期时间功能(如下所示)。然后我尝试使用DATEADD函数,但这并没有实现我想要找到的东西。有人知道如何实现这一目标吗?
/* Converting to DATETIME */
Data NEW_FILE ;
SET DATA.MY_FILE;
new_call_day = input(compress(call_day),yymmdd8.);
format new_call_day date9.;
new_time = input(compress(trans_time), HHMMSS8.);
format new_time HHMM5.;
dtetime = dhms(compress(new_call_day),0,0,compress(new_time));
format dtetime datetime22.
RUN;
在此之后我尝试了DATEADD,但它没有为我想要的每一个创建一个列。我被困了......也许我走错了?
答案 0 :(得分:3)
使用SQL子查询。它们很快(特别是如果你可以将执行权交给DBMS)并且易于维护。
PROC SQL NOPRINT;
CREATE TABLE Work.TransWithCount AS
SELECT STORE_NUM
, TERMINAL
, TRANS_DT
, (
SELECT COUNT(*)
FROM Work.Trans AS T
WHERE T.STORE_NUM = P.STORE_NUM
AND T.TERMINAL = P.TERMINAL
AND T.TRANS_DT >= P.START_DT
AND T.TRANS_DT <= P.END_DT
) AS TRANS_COUNT
FROM Work.Trans AS P
;
QUIT;
非SQL方法也是可行的,但它要复杂得多。请参阅下面的更新。
我不能保证这会快速执行,但就代码而言,这实际上是一个相当简单的SQL语句。如果可以将SQL传递给数据库服务器,则可以通过利用表索引来获得一些性能提升。
以下方法仅适用于SAS服务器。它创建了三个新列:一个将日期和时间组合成单个值(这使得处理午夜附近的事务变得更加简单)以及将用于连接彼此靠近的事务的开始和结束列。例如,如果您希望在五分钟内(+/- 5分钟)计算交易,而不是仅仅事先交易5分钟(-5分钟),则可以轻松修改开始和结束。
这里我创建的示例数据集与您的示例相同:
DATA Work.Trans;
INPUT CALL_DAY B8601DA8. +1
TRANS_TIME HHMMSS8.
STORE_NUM
TERMINAL
CUSTOMER_NUMBER
;
FORMAT CALL_DAY MMDDYY10.
TRANS_TIME TIME10.
STORE_NUM
TERMINAL
CUSTOMER_NUMBER
;
DATALINES;
20130201 10:46:04 1111 1 1
20130202 17:09:19 1111 2 2
20130202 17:10:30 2222 3 3
20130202 17:11:35 2222 3 3
20130202 17:13:26 2222 3 4
RUN;
现在我要创建三个新列并删除日期和时间列:
DATA Work.Trans;
SET Work.Trans;
FORMAT START_DT DATETIME18.
END_DT DATETIME18.
TRANS_DT DATETIME18.
;
TRANS_DT = DHMS( CALL_DAY,
HOUR(TRANS_TIME),
MINUTE(TRANS_TIME),
SECOND(TRANS_TIME) );
START_DT = TRANS_DT - '00:05:00't;
END_DT = TRANS_DT;
DROP CALL_DAY
TRANS_TIME
;
RUN;
最后,我将创建一个简单的SQL语句,该语句在同一数据集上执行子查询。对于父集中的每一行,子查询将根据商店和终端ID以及开始和结束日期(与交易日期相比)查找匹配的记录:
PROC SQL NOPRINT;
CREATE TABLE Work.TransWithCount AS
SELECT STORE_NUM
, TERMINAL
, TRANS_DT
, (
SELECT COUNT(*)
FROM Work.Trans AS T
WHERE T.STORE_NUM = P.STORE_NUM
AND T.TERMINAL = P.TERMINAL
AND T.TRANS_DT >= P.START_DT
AND T.TRANS_DT <= P.END_DT
) AS TRANS_COUNT
FROM Work.Trans AS P
;
QUIT;
瞧!您现在可以使用以下数据集:
┌───────────┬──────────┬────────────────────┬─────────────┐
│ STORE_NUM │ TERMINAL │ TRANS_DT │ TRANS_COUNT │
├───────────┼──────────┼────────────────────┼─────────────┤
│ 1111 │ 1 │ 01Feb2013 10:46:04 │ 1 │
│ 1111 │ 2 │ 02Feb2013 17:09:19 │ 1 │
│ 2222 │ 3 │ 02Feb2013 17:10:30 │ 1 │
│ 2222 │ 3 │ 02Feb2013 17:11:35 │ 2 │
│ 2222 │ 3 │ 02Feb2013 17:13:26 │ 3 │
└───────────┴──────────┴────────────────────┴─────────────┘
修改强>
我刚注意到TRANS_COUNT也会计算父行。如果这对你来说是个问题,那么就没有“biggie”:只需将计数减1,以确保你只计算其他交易:
PROC SQL NOPRINT;
CREATE TABLE Work.TransWithCount AS
SELECT STORE_NUM
, TERMINAL
, TRANS_DT
, (
SELECT COUNT(*) - 1
FROM Work.Trans AS T
WHERE T.STORE_NUM = P.STORE_NUM
AND T.TERMINAL = P.TERMINAL
AND T.TRANS_DT >= P.START_DT
AND T.TRANS_DT <= P.END_DT
) AS TRANS_COUNT
FROM Work.Trans AS P
;
QUIT;
┌───────────┬──────────┬────────────────────┬─────────────┐
│ STORE_NUM │ TERMINAL │ TRANS_DT │ TRANS_COUNT │
├───────────┼──────────┼────────────────────┼─────────────┤
│ 1111 │ 1 │ 01Feb2013 10:46:04 │ 0 │
│ 1111 │ 2 │ 02Feb2013 17:09:19 │ 0 │
│ 2222 │ 3 │ 02Feb2013 17:10:30 │ 0 │
│ 2222 │ 3 │ 02Feb2013 17:11:35 │ 1 │
│ 2222 │ 3 │ 02Feb2013 17:13:26 │ 2 │
└───────────┴──────────┴────────────────────┴─────────────┘
如果您不想使用SQL,您仍然可以从DATA步骤执行所有这些操作。我不是SAS专家,但我设计了以下解决方案。它基本上打开数据集并加载第一条记录,然后向前查看并计算记录,直到STORE_NUM或TERMINAL发生变化,TRANS_DT大于或小于我们已计算的开始和结束日期,或达到EOF。当满足其中一个条件时,将加载下一条记录并重复逻辑。
要使其正常工作,必须对数据集进行适当的排序(通过STORE_NUM和TERMINAL,然后通过TRANS_DT DESCENDING )。否则,偷看操作将会短路并且您的计数将不正确。
所以,首先我们排序:
PROC SORT DATA=Work.Trans;
BY STORE_NUM
TERMINAL
DESCENDING TRANS_DT
;
RUN;
然后我们运行逻辑来读取数据集 1 。涉及许多步骤,因此我使用注释来解释该过程的每个步骤:
DATA Work.Trans2;
FORMAT STORE_NUM 4.0
TERMINAL 1.0
TRANS_DT DATETIME18.
TRANS_COUNT 6.0
;
KEEP STORE_NUM
TERMINAL
TRANS_DT
TRANS_COUNT
;
/* OPEN THE Work.Trans DATASET */
TransId = OPEN( 'Work.Trans', 'IN' );
/* ITERATE OVER ALL OBSERVATIONS IN Work.Trans */
CURR_OBS = 1;
DO WHILE(1);
PUT 'CURR_OBS = ' CURR_OBS;
/* LOAD NEXT OBSERVATION */
NEXT_RC = FETCHOBS( TransId, CURR_OBS );
IF (NEXT_RC ~= 0) THEN LEAVE;
/* LOAD VALUES FROM THE CURRENT OBSERVATION */
STORE_NUM = GETVARN( TransId, 1 );
TERMINAL = GETVARN( TransId, 2 );
CUSTOMER_NUMBER = GETVARN( TransId, 3 );
TRANS_DT = GETVARN( TransId, 4 );
START_DT = GETVARN( TransId, 5 );
END_DT = GETVARN( TransId, 6 );
TRANS_COUNT = 0;
/* PEEK AHEAD TO COUNT TRANSACTIONS THAT OCCURRED WITHIN THE SPECIFIED
TIME RANGE */
PEEK_OBS = CURR_OBS + 1;
DO WHILE(1);
PUT 'PEEK_OBS = ' PEEK_OBS;
/* PEEK AHEAD TO NEXT OBSERVATION */
PEEK_RC = FETCHOBS( TransId, PEEK_OBS );
/* IF THE EOF IS REACHED, EXIT THE CURRENT DO LOOP
(STOP PEEKING) */
IF ( PEEK_RC ~= 0 ) THEN LEAVE;
PK_STORE_NUM = GETVARN( TransId, 1 );
PK_TERMINAL = GETVARN( TransId, 2 );
PK_TRANS_DT = GETVARN( TransId, 4 );
IF PK_STORE_NUM = STORE_NUM AND
PK_TERMINAL = TERMINAL AND
PK_TRANS_DT >= START_DT AND
PK_TRANS_DT <= END_DT
THEN DO;
/* IF THE STORE_NUM AND TERMINAL MATCH THE CURRENT OBSERVATION
AND THE TRANS_DT IS WITHIN THE ACCEPTABLE RANGE THEN
INCREMENT TRANS_COUNT BY 1 */
TRANS_COUNT + 1;
END;
/* OTHERWISE, EXIT THE CURRENT DO LOOP (STOP PEEKING AHEAD) */
ELSE LEAVE;
/* INCREMENT PEEK INDEX BY 1 */
PEEK_OBS + 1;
END;
/* OUTPUT THE CURRENT RECORD ALONG WITH THE TRANS_COUNT TO
Work.Trans2 */
OUTPUT;
/* INCREMENT CURRENT OBSERVATION INDEX BY 1 */
CURR_OBS + 1;
END;
/* EXPLICITLY CLOSING THE Work.Trans DATASET IS OPTIONAL IN THIS CONTEXT,
BUT GOOD PRACTICE */
CLOSE_RC = CLOSE( TransId );
RUN;
最后,根据需要对生成的数据集进行排序。我已将数据集返回到源数据集中最初找到的排序(TRANS_DT 升序)。
PROC SORT DATA=Work.Trans2;
BY STORE_NUM
TERMINAL
TRANS_DT
;
RUN;
结果与上面的第二个SQL解决方案相同。 (如果您喜欢第一个解决方案,那么只需将TRANS_COUNT默认为1而不是0)
┌───────────┬──────────┬────────────────────┬─────────────┐
│ STORE_NUM │ TERMINAL │ TRANS_DT │ TRANS_COUNT │
├───────────┼──────────┼────────────────────┼─────────────┤
│ 1111 │ 1 │ 01Feb2013 10:46:04 │ 0 │
│ 1111 │ 2 │ 02Feb2013 17:09:19 │ 0 │
│ 2222 │ 3 │ 02Feb2013 17:10:30 │ 0 │
│ 2222 │ 3 │ 02Feb2013 17:11:35 │ 1 │
│ 2222 │ 3 │ 02Feb2013 17:13:26 │ 2 │
└───────────┴──────────┴────────────────────┴─────────────┘
在某些情况下,这种无SQL方法可能会更快。如果您没有设置索引并且您的数据被分解成小块,以便您只是向前扫描一小部分记录,那么这可能比SQL子查询更快(在SAS上运行 - DBMS可能更快即使没有索引)。我没有针对超大规模数据集对其进行测试,因此我无法验证这些声明。
1 我欠了文章的“Trans2”代码 Data Without (Step) Boundaries: Using Data Access Functions by Felix Galbis-Reig