Sql 基于活动数据创建id列

Sql 基于活动数据创建id列,sql,oracle,oracle11g,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

我有一个表
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

你有什么想法吗?

我想这可以满足你的需要。我将输入中的“user”改为“usr”,将输出中的“session”改为“sess”——我从未对对象名称使用过Oracle保留字

注意:正如Boneist在下面指出的,我的解决方案将为第一个会话分配一个会话号
0
,如果它是一个
注销
事件(或顶部的一系列
注销
)。如果数据中可能出现这种情况,并且即使在这种情况下,所需的行为也是将会话计数开始为1,则必须调整
标志的定义-例如,当
滞后(事件)超过(按usr顺序按事件划分)为空时,使
标志=1

祝你好运

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\u时间戳\u格式
设置):


我认为这会起作用:

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
此解决方案依赖于案例表达式中发生的逻辑短路以及事件类型不为null的事实。它还假设一行中的多个注销被计为单独的会话:

  • 如果前一行是注销行(如果没有前一行-即集合中的第一行-将其视为存在注销行),我们希望将计数器增加1。(注销会终止会话,因此注销后我们始终会有一个新会话。)
  • 如果当前行是注销,则会终止现有会话。因此,不应增加计数器
  • 如果当前行的时间比前一行的时间长一个小时,请将计数器增加一个小时
  • 如果当前行是登录行,则它是一个新会话,因此将计数器增加1
  • 对于任何其他情况,我们不增加计数器
  • 一旦我们做到了这一点,就只需要在计数器上计算一个运行总数。

    为了完整性(对于使用Oracle 12或更高版本的用户),这里有一个使用
    MATCH\u RECOGNIZE
    的解决方案:

    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
                          )
    )
    ;
    
    选择usr、事件、事件类型、sess
    从事件中
    相配(
    usr分区
    按事件排序\u ts
    测量值与sess匹配_number()
    每场比赛的所有行
    模式(strt follow*)
    将follow定义为事件类型='Logout'
    或(事件类型!=“登录”
    和上一个(事件类型)!=“注销”
    事件数<上一个(事件数)+1/24
    )
    )
    ;
    
    这里我将介绍一个不寻常的情况:在另一个
    注销
    事件之后发生
    注销
    事件。在这种情况下,我假设所有连续的
    注销
    都属于同一个会话,无论时间间隔有多少。(如果保证数据中不会出现这种情况,那就更好了。)


    另请参见我在另一个答案(针对Oracle 11及以下版本)中添加的关于
    usr
    作为
    注销的第一个事件的可能性的注释(如果在输入数据中可能)。

    编写代码将是一个良好的开端。我们不是来为您这样做的,也不是来告诉您如何进行项目的。由于第一个会话将在09:25过期,您的第三行如何在没有其他登录事件的情况下存在?如果你有额外的登录,那么这看起来相当简单;如果不是的话,那就更难了……所以,我要明确一点——对于给定的用户来说,新的会话是在新事件距离上次事件>1小时(或者是>=,请澄清!)时开始的,还是在注销后开始的,不管时间如何?还可以保证在注销后,下一个会话将以登录而不是其他方式开始吗?(除了第一次登录之外,是否可以在没有注销的情况下登录?@AlexPoole-“更有趣”=“更有趣”{:-)@AlexPoole如果超时,没有事件发送到服务器,没有新的登录记录(有趣吧?)这是一个小问题,我不确定它是否与OP有关,但如果您有一个usr,其第一行是注销行,则sess将为0。您需要在延迟(事件类型)中考虑到这一点,将前一行空视为注销行,以便将该情况包括在sess计数中(当然,假设事件类型不是null!)@Boneist-是的,我考虑过这一点-我做出了一个有意识的选择,假设输入数据保证第一个事件不能是
    注销
    。不过我应该提到它。请参阅我另一个答案中的最后一段(针对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
                          )
    )
    ;