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
)
)
;