HQL || SQL练习:用户登录区间合并

  • 有一张用户行为日志表ods_user_log, 包含user_id(用户id)、start_time(登录时间)以及end_time(注销时间)
  • 问题:求出用户登录的所有最大时间段,比如用户1在10:00登录且11:00注销,用户2在10:30登录且12:00注销,那么最大时间段就是 10:0012:00
CREATE TABLE ods_user_log (
    user_id INT PRIMARY KEY,
    start_time DATETIME,
    end_time DATETIME
);

INSERT INTO ods_user_log (user_id, start_time, end_time)
VALUES
    (1, '2024-07-01 08:00:00', '2024-07-01 10:00:00'),
    (2, '2024-07-01 09:30:00', '2024-07-01 11:30:00'), 
    (3, '2024-07-01 10:00:00', '2024-07-01 12:00:00'), 
    (4, '2024-07-01 11:00:00', '2024-07-01 13:00:00'), 
    (5, '2024-07-02 08:00:00', '2024-07-02 10:00:00'),
    (6, '2024-07-02 09:00:00', '2024-07-02 11:00:00'), 
    (7, '2024-07-02 10:30:00', '2024-07-02 12:30:00'), 
    (8, '2024-07-02 11:00:00', '2024-07-02 13:00:00'),
    (9, '2024-07-03 08:00:00', '2024-07-03 10:00:00'),
    (10, '2024-07-03 09:00:00', '2024-07-03 11:00:00'); 
  

解析:

要找出用户登录的所有最大时间段,我们只需要两列数据:start_time(登录时间)和 end_time(注销时间)。user_id 并不需要参与计算。

如何判断时间段是否重叠?
可以通过比较当前记录的 start_time 和前一条记录的 end_time(可以用 pre_end_time 表示)来确定:

  • 如果当前记录的 start_time <= 前一条记录的 end_time,说明这两个时间段是重叠的。此时,给当前记录打上标签 flag = 0
  • 如果不重叠,则标签为 flag = 1。表示不重叠,为新的区间。

注意:
需要保证记录是按时间顺序的,所以要按照start_time(登录时间)和 end_time(注销时间)升序排列数据。

with t1 as (
  select
   start_time,
   end_time,
   lag(end_time,1) over(order by start_time, end_time) as pre_end_time
  from ods_user_log
)
    select 
      start_time,
      end_time,
      case 
      when start_time <= pre_end_time
      then 0 else 1
      end as flag
  from t1


当执行完上面语句后,过程结果如下:
start_time	           end_time	       flag
2024-07-01 08:00:00	2024-07-01 10:00:00	1
2024-07-01 09:30:00	2024-07-01 11:30:00	0
2024-07-01 10:00:00	2024-07-01 12:00:00	0
2024-07-01 11:00:00	2024-07-01 13:00:00	0
2024-07-02 08:00:00	2024-07-02 10:00:00	1
2024-07-02 09:00:00	2024-07-02 11:00:00	0
2024-07-02 10:30:00	2024-07-02 12:30:00	0
2024-07-02 11:00:00	2024-07-02 13:00:00	0
2024-07-03 08:00:00	2024-07-03 10:00:00	1
2024-07-03 09:00:00	2024-07-03 11:00:00	0

flag 0 变为 1 时,表示开始了一个新的时间区间,因为没有与前一个时间段重叠。这意味着我们需要新增一个时间交叉分组。
因此,可以对 flag 进行累加,每次累加后的值就是一个新的分组 ID。

t3 as (
  select 
    start_time,
    end_time,
    sum(flag) over(order by start_time, end_time) as group_id
  from t2
)
执行完该语句后结果如下:
start_time	         end_time	     group_id
2024-07-01 08:00:00	2024-07-01 10:00:00	1
2024-07-01 09:30:00	2024-07-01 11:30:00	1
2024-07-01 10:00:00	2024-07-01 12:00:00	1
2024-07-01 11:00:00	2024-07-01 13:00:00	1
2024-07-02 08:00:00	2024-07-02 10:00:00	2
2024-07-02 09:00:00	2024-07-02 11:00:00	2
2024-07-02 10:30:00	2024-07-02 12:30:00	2
2024-07-02 11:00:00	2024-07-02 13:00:00	2
2024-07-03 08:00:00	2024-07-03 10:00:00	3
2024-07-03 09:00:00	2024-07-03 11:00:00	3

最后选择同组的min(start_time)max(end_time)即为登录最大时间段

SELECT   
    group_id, -- 每个时间段组的ID
    MIN(start_time) AS start_time, 
    MAX(end_time) AS end_time 
FROM 
    t3
GROUP BY 
    group_id; 
结果如下:
group_id	start_dt	end_dt
1	2024-07-01 08:00:00	2024-07-01 13:00:00
2	2024-07-02 08:00:00	2024-07-02 13:00:00
3	2024-07-03 08:00:00	2024-07-03 11:00:00

整体语句: 

 with t1 as (
   SELECT
     start_time,
     end_time,
     lag(end_time,1) over(order by start_time, end_time) as pre_time
   from ods_user_log
 ),
 t2 as (
   SELECT  
     start_time,
     end_time,
    case 
     when start_time <= pre_time 
    then 0 else 1
   end as flag
 from t1
 ),
 t3 as (
    select 
        start_time,
        end_time,
        sum(flag) over(order by start_time, end_time) as group_id
   from t2
 )
 SELECT
    group_id,
    min(start_time) as start_dt,
    max(end_time) as end_dt
    from t3
    group by group_id

小细节:

如果使用 lag(end_time, 1) over (order by start_time, end_time) as pre_end_time,对于第一条记录,pre_end_time 会是 NULL,从而确保第一条记录始终被视为一个独立的分组,并且分组从 1 开始。

相反,如果使用 lag(end_time, 1, end_time) over (order by start_time, end_time) as pre_end_time 来计算 pre_end_time,那么在处理第一条记录时,由于没有前一条记录,pre_end_time 会被赋值为当前记录的 end_time。这会导致第一条记录与自己“交叉”,从而使分组从 0 开始。

start_time	           end_time	       flag
2024-07-01 08:00:00	2024-07-01 10:00:00	0
2024-07-01 09:30:00	2024-07-01 11:30:00	0
2024-07-01 10:00:00	2024-07-01 12:00:00	0
2024-07-01 11:00:00	2024-07-01 13:00:00	0
2024-07-02 08:00:00	2024-07-02 10:00:00	1
2024-07-02 09:00:00	2024-07-02 11:00:00	0
2024-07-02 10:30:00	2024-07-02 12:30:00	0
2024-07-02 11:00:00	2024-07-02 13:00:00	0
2024-07-03 08:00:00	2024-07-03 10:00:00	1
2024-07-03 09:00:00	2024-07-03 11:00:00	0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值