簡體   English   中英

計算特定日期,特定商店,過去5分鍾內特定終端的過去5分鍾內的交易次數

[英]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,但它沒有為我想要的每一個創建一個列。 我被卡住了......也許我走錯了?

TL; DR

使用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 │
└───────────┴──────────┴────────────────────┴─────────────┘

UPDATE

如果您不想使用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM