两种方法解决SQL连续登录问题
目录
- 一、需求及数据准备
- 二、SQL处理逻辑
- (1)连续序列构造法
- (2)lag+累计求和构造法
- 三、总结
一、需求及数据准备
需求:现有一张用户登录表user_login_records
记录用户一天内登录某个APP的时间,需要通过这张表找出至少连续登录N次的用户(或者每个用户最大连续登录次数)。连续定义:同一个用户相邻两条登录记录之前,没有其他用户登录记录(用户登录表内数据默认按登录时间依次升序排列)
CREATE TABLE `user_login_records` (`app_id` int NOT NULL COMMENT '应用ID',`user_id` int NOT NULL COMMENT '用户ID',`login_time` datetime NOT NULL COMMENT '登录时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户登录记录表'-- 批量插入用户登录数据
INSERT INTO user_login_records (app_id, user_id, login_time)
VALUES
(1, 1, '2025-10-20 08:20:00'),
(1, 2, '2025-10-20 08:45:00'),
(1, 2, '2025-10-20 08:46:00'),
(1, 2, '2025-10-20 09:22:00'),
(1, 3, '2025-10-20 10:15:00'),
(1, 3, '2025-10-20 10:16:00'),
(1, 2, '2025-10-20 10:20:00'),
(1, 2, '2025-10-20 11:00:00'),
(1, 2, '2025-10-20 11:20:00'),
(1, 2, '2025-10-20 11:22:00'),
(1, 6, '2025-10-20 14:00:00'),
(1, 6, '2025-10-20 14:05:00'),
(1, 6, '2025-10-20 14:25:00'),
(1, 6, '2025-10-20 14:30:00'),
(1, 1, '2025-10-20 16:30:00');
二、SQL处理逻辑
(1)连续序列构造法
思路:若用户连续登录,则记录序号必定连续。可构造2组连续的序列,相减即可获得一组常数列,即可用来区分不同的连续记录。一组连续可按所有人登录时间进行排序处理,一组可按每个用户登录时间进行分组排序。(注意:本题是求解同一天内用户连续登录多次的场景,如果是求解连续登录多天的情况,则需要对每天内登录多次的记录进行去重再处理)
select user_id ,rk_sequence,count(*) as constant_length
from
(select user_id,login_time,all_rk,user_rk ,all_rk-user_rk as rk_sequencefrom (select user_id ,login_time,row_number() over(order by login_time asc ) as all_rk ,row_number() over(partition by user_id order by login_time asc) as user_rk from user_login_records) t1
) t2
group by user_id ,rk_sequence
其中,rk_sequence
可理解为序列连续序列起始的位置,cosntant_length
为连续序列的长度;以用户2进行举例说明,用户2在一天内有2组连续登录的记录,一组连续登录了3次,一组连续登录了4次;在上述结果的基础上,求解连续登录N次和最大连续登录次数,只需要对结果进行聚合筛选即可
--(1)求解至少连续登录N次的用户
select distinct user_id
from
(select user_id,login_time,all_rk,user_rk ,all_rk-user_rk as rk_sequencefrom (select user_id ,login_time,row_number() over(order by login_time asc ) as all_rk ,row_number() over(partition by user_id order by login_time asc) as user_rk from user_login_records) t1
) t2
group by user_id ,rk_sequence
having count(*)>=N--(2)求解每个用户最大连续登录次数
select user_id ,max(constant_length) as max_cosntant_length
from
(select user_id ,rk_sequence,count(*) as constant_lengthfrom (select user_id,login_time,all_rk,user_rk ,all_rk-user_rk as rk_sequencefrom (select user_id ,login_time,row_number() over(order by login_time asc ) as all_rk ,row_number() over(partition by user_id order by login_time asc) as user_rk from user_login_records) t1) t2group by user_id ,rk_sequence
) t3
group by user_id
(2)lag+累计求和构造法
思路:
(1)首先需要对连续登录的用户进行标记,创建1个辅助列is_constant
,通过lag
函数比较当前记录和上一条记录的用户ID,如果相同,则表明该条为连续记录;如果不同,则表明为间断记录。
(2)其次,需要对这些连续记录进行分组标记,便于统计连续记录的条数。考虑当该条记录连续时,is_constant
则为1,计算1-is_constant
,则该数值为0;如果不连续,则1-is_constant
对应数值为1;那么当我们对这列数1-is_constant
进行累计求和时,则会得到这样的效果:当记录连续时,累计和不变;当记录一旦间断时,累计和将会发生变化,这样就可以实现对连续记录进行标记的效果
(3)对分组的连续记录计数,筛选即可
过程:
(1)对连续登录用户进行标记
select user_id ,login_time ,case when user_id = lag(user_id,1) over(order by login_time asc) then 1 else 0 end as is_constantfrom user_login_records
(2)对连续记录进行分组标记
select user_id ,login_time,is_constant,sum(1-is_constant) over(order by login_time asc ) as constant_flag from (select user_id ,login_time ,case when user_id = lag(user_id,1) over(order by login_time asc) then 1 else 0 end as is_constantfrom user_login_records) t
(3)只要构建出连续分组标记,最终只要聚合计数统计即可
-- 1、统计至少连续登录N次的用户ID
select distinct user_id
from
(select user_id ,login_time,is_constant,sum(1-is_constant) over(order by login_time asc ) as constant_flag from (select user_id ,login_time ,case when user_id = lag(user_id,1) over(order by login_time asc) then 1 else 0 end as is_constantfrom user_login_records) t
) tt
group by user_id,constant_flag
having count(*)>=N-- 2、统计每个用户最大连续登录次数
select user_id,max(constant_length) as constant_length
from
(
select user_id ,constant_flag ,count(*) as constant_length
from
(select user_id ,login_time,is_constant,sum(1-is_constant) over(order by login_time asc ) as constant_flag from (select user_id ,login_time ,case when user_id = lag(user_id,1) over(order by login_time asc) then 1 else 0 end as is_constantfrom user_login_records) t
) tt
group by user_id,constant_flag
) ttt
group by user_id
三、总结
方法一和二都是通过构建序列的方式来对连续记录进行分组标记;方法一主要是通过常数列=自增序列1-自增序列2
方式进行构建,方法二则比较巧妙,需要通过连续记录标记的累计和,进行分组标记