计算过去5分钟内特定日期、特定商店、特定终端的交易数量

  • 本文关键字:终端 交易 5分钟 过去 日期 计算 sql sas
  • 更新时间 :
  • 英文 :


我正在计算当前事务在过去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分钟内,在特定的一天、特定的商店号码、特定的终端发生了多少笔交易,并为每一行创建一列,说明每笔交易的号码。

到目前为止,我已经将日期和时间转换为datetime函数(如下所示)。然后我尝试使用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方法也是可能的,但它要复杂得多。请参阅下面的更新。

Main

我不能保证它会很快执行,但就代码而言,这实际上是一个相当琐碎的SQL语句。如果您可以将SQL传递给数据库服务器,那么您可能能够通过利用表索引来获得一些性能提升。

以下方法仅适用于SAS服务器。它创建了三个新列:一个将日期和时间组合为一个值(这使处理接近午夜的事务变得更简单),另一个将用于连接彼此接近的事务的开始和结束列。例如,如果您希望在5分钟内(+/-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也会计算父行。如果这对你来说是个问题,那么没有"大问题":只需将计数减去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_DTDESCENDING)。否则,peek操作将短路,您的计数将不正确。

因此,首先我们对进行排序

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

的文章《无(步骤)边界的数据:使用数据访问函数》

相关内容

最新更新