[英]Counting number of transactions within past 5 minutes on a particular day, at a particular store, at a particular terminal within the past 5 minutes
我正在嘗試計算當前交易過去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,但它沒有為我想要的每一個創建一個列。 我被卡住了......也許我走錯了?
使用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”代碼歸功於文章 數據無(步驟)邊界:使用數據訪問函數Felix Galbis-Reig
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.