根据活动数据创建id列

时间:2016-10-19 14:08:06

标签: sql oracle oracle11g

我有一张表EVENTS

USER  EVENT_TS             EVENT_TYPE
abc   2016-01-01 08:00:00  Login
abc   2016-01-01 08:25:00  Stuff
abc   2016-01-01 10:00:00  Stuff
abc   2016-01-01 14:00:00  Login
xyz   2015-12-31 18:00:00  Login
xyz   2016-01-01 08:00:00  Logout

我需要做的是为每个用户的每个活动周期生成一个session字段。此外,如果用户闲置的时间等于或长于p_timeout(在这种情况下为1小时),则新会话将在下一个活动开始。用户并不总是干净利落地注销,因此注销不会在那里走路......

注意:

注销总是终止会话
没有必要注销或登录(因为软件)
登录始终是新会话

输出如

USER  EVENT_TS             EVENT_TYPE  SESSION
abc   2016-01-01 08:00:00  Login       1
abc   2016-01-01 08:25:00  Stuff       1
abc   2016-01-01 10:00:00  Stuff       2
abc   2016-01-01 14:00:00  Login       3
xyz   2015-12-31 18:00:00  Login       1
xyz   2016-01-01 08:00:00  Logout      1

关于如何实现这一点的任何想法?

3 个答案:

答案 0 :(得分:5)

我认为这会解决问题:

WITH EVENTS AS (SELECT 'abc' usr, to_date('2016-01-01 08:00:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'login' event_type FROM dual UNION ALL
                SELECT 'abc' usr, to_date('2016-01-01 08:25:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'Stuff' event_type FROM dual UNION ALL
                SELECT 'abc' usr, to_date('2016-01-01 10:00:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'Stuff' event_type FROM dual UNION ALL
                SELECT 'abc' usr, to_date('2016-01-01 14:00:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'login' event_type FROM dual UNION ALL
                SELECT 'xyz' usr, to_date('2015-12-31 18:00:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'login' event_type FROM dual UNION ALL
                SELECT 'xyz' usr, to_date('2016-01-01 08:00:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'Logout' event_type FROM dual UNION ALL
                SELECT 'def' usr, to_date('2016-01-01 08:00:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'Logout' event_type FROM dual UNION ALL
                SELECT 'def' usr, to_date('2016-01-01 08:15:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'Logout' event_type FROM dual)
SELECT usr,
       event_ts,
       event_type,
       SUM(counter) OVER (PARTITION BY usr ORDER BY event_ts) session_id
FROM   (SELECT usr,
               event_ts,
               event_type,
               CASE WHEN LAG(event_type, 1, 'Logout') OVER (PARTITION BY usr ORDER BY event_ts) = 'Logout' THEN 1
                    WHEN event_type = 'Logout' THEN 0
                    WHEN event_ts - LAG(event_ts) OVER (PARTITION BY usr ORDER BY event_ts) > 1/24 THEN 1
                    WHEN event_type = 'login' THEN 1
                    ELSE 0
               END counter
        FROM   EVENTS);

USR EVENT_TS            EVENT_TYPE SESSION_ID
--- ------------------- ---------- ----------
abc 2016-01-01 08:00:00 login               1
abc 2016-01-01 08:25:00 Stuff               1
abc 2016-01-01 10:00:00 Stuff               2
abc 2016-01-01 14:00:00 login               3
def 2016-01-01 08:00:00 Logout              1
def 2016-01-01 08:15:00 Logout              2
xyz 2015-12-31 18:00:00 login               1
xyz 2016-01-01 08:00:00 Logout              1

此解决方案依赖于CASE表达式中发生的逻辑短路以及event_type不为空的事实。它还假设连续多次注销被视为单独的会话:

  1. 如果前一行是注销行(并且如果没有前一行 - 即对于集合中的第一行 - 将其视为注销行存在),我们希望将计数器增加一。 (注销会终止会话,因此我们在注销后总是会有一个新会话。)
  2. 如果当前行是注销,则会终止现有会话。因此,不应该增加计数器。
  3. 如果当前行的时间大于上一行的一小时,请将计数器增加一。
  4. 如果当前行是登录行,那么它是一个新会话,因此将计数器增加一个。
  5. 对于任何其他情况,我们不会增加柜台。
  6. 一旦我们完成了这项工作,只需要在柜台上进行总计运行。

答案 1 :(得分:4)

我认为这可能会满足您的需求。我改变了#34;用户"到" usr"在输入和"会话"到" sess"在输出中 - 我没有使用保留的Oracle单词作为对象名称。

注意:正如Boneist在下面指出的那样,我的解决方案会将会话编号0分配给第一个会话,如果它是Logout事件(或继承) Logout的权利在顶部)。如果数据中出现这种情况,并且即使在这种情况下,如果所需行为是将会话计数设置为1,那么必须调整flag的定义 - 例如,在flag = 1时进行调整lag(event_ts) over (partition by usr order by event_ts) is null也是如此。

祝你好运!

with
     events ( usr, event_ts, event_type ) as (
       select 'abc', to_timestamp('2016-01-01 08:00:00', 'yyyy-mm-dd hh24:mi:ss'), 'Login' from dual union all
       select 'abc', to_timestamp('2016-01-01 08:25:00', 'yyyy-mm-dd hh24:mi:ss'), 'Stuff' from dual union all
       select 'abc', to_timestamp('2016-01-01 10:00:00', 'yyyy-mm-dd hh24:mi:ss'), 'Stuff' from dual union all
       select 'abc', to_timestamp('2016-01-01 14:00:00', 'yyyy-mm-dd hh24:mi:ss'), 'Login' from dual union all
       select 'xyz', to_timestamp('2015-12-31 18:00:00', 'yyyy-mm-dd hh24:mi:ss'), 'Login' from dual union all
       select 'xyz', to_timestamp('2016-01-01 08:00:00', 'yyyy-mm-dd hh24:mi:ss'), 'Logout' from dual
     ),
     start_of_sess ( usr, event_ts, event_type, flag ) as (
       select usr, event_ts, event_type,
              case when event_type != 'Logout' 
                    and
                        (    event_ts >= lag(event_ts) over (partition by usr 
                                                             order by event_ts) + 1/24 
                          or event_type = 'Login'
                          or lag(event_type) over (partition by usr 
                                                   order by event_ts) = 'Logout'
                        )
                   then 1 end
       from   events
     )
select usr, event_ts, event_type,
       count(flag) over (partition by usr order by event_ts) as sess
from   start_of_sess
; 

输出(时间戳使用我当前的NLS_TIMESTAMP_FORMAT设置):

USR EVENT_TS                          EVENT_TYPE   SESS
--- --------------------------------- ---------- ------
abc 01-JAN-2016 08.00.00.000000000 AM Login           1
abc 01-JAN-2016 08.25.00.000000000 AM Stuff           1
abc 01-JAN-2016 10.00.00.000000000 AM Stuff           2
abc 01-JAN-2016 02.00.00.000000000 PM Login           3
xyz 31-DEC-2015 06.00.00.000000000 PM Login           1
xyz 01-JAN-2016 08.00.00.000000000 AM Logout          1

 6 rows selected     

答案 2 :(得分:2)

为了完整性(对于使用Oracle 12或更高版本的用户),以下是使用select usr, event_ts, event_type, sess from events match_recognize( partition by usr order by event_ts measures match_number() as sess all rows per match pattern (strt follow*) define follow as event_type = 'Logout' or ( event_type != 'Login' and prev(event_type) != 'Logout' and event_ts < prev(event_ts) + 1/24 ) ) ; 的解决方案:

Logout

这里我介绍了一个不寻常的案例:另一个Logout事件后的Logout事件。在这种情况下,我假设所有连续的usr s,无论多少时间和相隔多远,都属于同一个会话。 (如果保证不会在数据中出现这种情况,那就更好了。)

另请参阅注意我添加到我的其他答案(适用于Oracle 11及更低版本),关于Logout作为{{1}}的第一个事件的可能性(如果在输入数据中甚至可以这样做的话)。