SQL 获取两个事件之间的会话持续时间

SQL 获取两个事件之间的会话持续时间

问题描述

我有一个数据集(具体是在PostgreSQL中),我需要计算“用户会话时间”。我非常确定需要使用分区来回答这个问题,但我不太确定如何去做。 events表包含id、user_id、事件类别和日期。当用户登录时,我们会在表中插入一个事件。每次他们访问一个页面,都会有一个类似的事件。表的结构类似于这样:

id user_id category date
1 100 Sign-In 2023-12-20 01:07:03
2 100 Page View 2023-12-20 01:07:05
3 100 Page View 2023-12-20 01:09:17
4 107 Sign-In 2023-12-20 01:09:19
5 107 Page View 2023-12-20 01:09:21
6 107 Page View 2023-12-20 01:09:50
7 100 Page View 2023-12-20 01:11:28
.. .. .. ..
784 100 Sign-In 2023-12-21 17:37:31

要从用户100计算会话时长,假设事件7是他们在该会话中系统中最后一页的浏览记录。我们需要获取登录事件(id为1),找到登录事件之前的最后一个事件(id为7),然后相减这两个时间。结果为2023-12-20 01:11:28 - 2023-12-20 01:07:03,即4分钟25秒。我希望对数据集中的每个用户/登录组合都进行这样的计算。完整性起见,用户可能没有任何页面浏览记录,这种情况下他们的会话时长为零。结果应该类似于这样:

user_id session_start duration
100 2023-12-20 01:07:03 00:04:25
100 2023-12-21 17:37:31 null (or 00:00:00)
107 2023-12-20 01:09:19 00:00:31

我猜我想要以用户为基础进行分区,然后再按事件类别分区,但是我需要选择的行的前一行,并不是下一个窗口。在代码中,我通常会取出所有的事件,按用户和持续时间排序,然后通过迭代创建一个新的结果集,每次都在登录事件发生时创建一个“会话”。我只是好奇是否可以在数据库中通过SQL更有效地完成这个任务。

解决方案

如果你觉得这个窗口函数很别扭,你可以用笨拙的标量子查询来替代。毕竟,从每一行“登录”开始,你只需要寻找同一用户的下一个“登录”之前的最大日期: demo1

select user_id,
       date as session_start,
       (select max(date) from test t2 
        where t2.user_id=t1.user_id
        and t2.date>=t1.date
        and category<>'Sign-In'
        and date<=(
            select coalesce(min(t3.date),'infinity'::timestamp) 
            from test t3 where category='Sign-In'
            and t3.date>t1.date and t3.user_id=t1.user_id)
       )-date as duration
from test t1
where category='Sign-In'
order by 1,2;
用户ID 会话开始时间 持续时间
100 2023-12-20 01:07:03+00 00:04:25
100 2023-12-21 17:37:31+00 null
107 2023-12-20 01:09:19+00 00:00:31

或者你可以继续使用窗口函数: demo2

select user_id, date as session_start, duration
from ( select *,(lead(date)over w2)-date as duration
       from ( select *,('Sign-In'=category) as is_session_start
                      ,('Sign-In'<>category and
                        'Sign-In'=lead(category,1,'Sign-In')over w1
                       ) as is_session_end
              from test t1
              window w1 as (partition by user_id order by date) 
       ) AS subquery1 where is_session_start or is_session_end
       window w2 as (partition by user_id order by date) 
) AS subquery2 where is_session_start
order by 1,2;

Camera课程

Python教程

Java教程

Web教程

数据库教程

图形图像教程

办公软件教程

Linux教程

计算机教程

大数据教程

开发工具教程