连续登录(连续交易、连续打卡、连续逾期)是 SQL 面试的高频经典题型,核心考察窗口函数与分组聚合的组合运用,本质是通过构造统一分组标记 ,将连续的记录归为同一组,再做聚合统计。常见分为两个核心变形:严格连续登录次数统计 、允许间隔 1 天的连续天数统计,下面分别拆解思路与实现。
5.1 连续登录次数最大的用户(严格连续)
题目描述
给定用户登录表,每条记录代表一次登录行为,找出连续登录次数最多的用户。连续定义为:登录日期严格连续,中间没有任何中断。
核心思路:日期 - 序号 分组法
这是严格连续问题的标准解法,核心逻辑: 对同一用户的登录日期按升序依次编号,连续的日期减去递增的序号,得到的差值必然完全相同。将 "用户 + 差值" 作为分组键,每组内的记录就是一段完整的连续登录,统计每组长度即可得到连续次数。
分步拆解
- 窗口排序编号 按用户分区,登录日期升序排列,用
ROW_NUMBER()生成每个用户内部的连续序号。 - 构造分组标记 用登录日期减去对应序号,得到差值
sn1。同一段连续登录的日期,减去对应序号后结果完全一致。 - 分组统计次数 按
用户 + 差值sn1分组,统计每组的登录次数。 - 排序取最大值 按次数降序排序,取排名第一的记录,即为连续登录次数最多的用户。
完整 SQL 实现
SELECT *
FROM (
SELECT
id,
sn1,
COUNT(*) AS login_cnt
FROM (
SELECT
id,
dt,
ROW_NUMBER() OVER(PARTITION BY id ORDER BY dt ASC) AS sn,
-- 日期减序号,构造连续分组标记
TO_DATE(dt, 'yyyy-mm-dd') - ROW_NUMBER() OVER(PARTITION BY id ORDER BY dt ASC) AS sn1
FROM TEST11 t
)
GROUP BY id, sn1
ORDER BY login_cnt DESC
)
WHERE ROWNUM = 1;
语法说明:以上为 Oracle 语法;MySQL 中可使用
DATE_SUB(dt, INTERVAL sn DAY)实现日期减序号。
易错点与扩展
- 单日去重 :若用户同一天多次登录,需先通过
DISTINCT id,dt去重,避免序号膨胀导致分组错误。 - 并列第一处理 :
ROWNUM=1仅取一条结果;若需返回所有并列最多的用户,改用RANK() OVER(ORDER BY login_cnt DESC)=1。 - 场景复用:该方法可直接扩展到连续交易、连续打卡、连续逾期等所有严格连续场景。
5.2 最大连续登录天数(允许间隔一天)
题目描述
允许登录中间间隔 1 天,仍视为连续。例如:1 月 1 日登录、1 月 2 日未登录、1 月 3 日登录,认定为连续登录 3 天。求每个用户的最大连续登录天数。
核心思路:双层分组法
相比严格连续,允许间隔 1 天的场景需要两次分组递进:
- 先按严格连续规则,把无中断的登录归为多个小段;
- 再对这些小段做二次分组,把间隔 1 天的小段合并为一个大段,最终计算大段的天数跨度。 核心技巧:用
DENSE_RANK()给严格连续段编号,再用日期减去排名,间隔 1 天的段会得到相同的分组标记,自动合并。
分步拆解
- 第一层:严格连续分段 同 5.1 逻辑,用
日期 - ROW_NUMBER()得到sub1,将严格连续的登录归为一组。 - 给连续段排名 按用户分区,对
sub1升序做DENSE_RANK()得到rk,每个严格连续段对应一个固定排名。 - 第二层:构造合并分组标记 用原始日期减去
rk天,得到sub2。间隔 1 天的两个严格连续段,减去对应排名后,差值会相同,从而被归为同一大组。 - 计算连续天数 按
用户 + sub2分组,用最大日期 - 最小日期 + 1计算该段连续天数。 - 取用户最大值 按用户分组,取最大的连续天数。
完整 SQL 实现
SELECT
id,
MAX(days) AS max_continuous_days
FROM (
SELECT
id,
DATEDIFF(MAX(dt), MIN(dt)) + 1 AS days
FROM (
SELECT
id,
dt,
rn,
sub1,
rk,
-- 二次构造分组标记,合并间隔1天的连续段
DATE_SUB(sub1, INTERVAL rk DAY) AS sub2
FROM (
SELECT
id,
dt,
rn,
sub1,
-- 给严格连续段排名
DENSE_RANK() OVER(PARTITION BY id ORDER BY sub1) AS rk
FROM (
SELECT
id,
dt,
rn,
-- 第一层:严格连续分组标记
DATE_SUB(dt, INTERVAL rn DAY) AS sub1
FROM (
SELECT
id,
dt,
ROW_NUMBER() OVER(PARTITION BY id ORDER BY dt) AS rn
FROM test1
) t1
) t2
) t3
) t4
GROUP BY id, sub2
) t5
GROUP BY id;
语法说明:日期函数可根据数据库适配:Oracle 可直接用日期加减运算,MySQL 使用
DATE_SUB、DATEDIFF。
易错点与扩展
- 间隔定义澄清:本题 "允许间隔一天" 指中间空 1 天(日期差 2 天)仍算连续;若允许间隔 N 天,只需调整二次分组的偏移量。
- 为什么用 DENSE_RANK 不用 ROW_NUMBER :
DENSE_RANK保证每个严格连续段排名唯一且连续,避免间隔导致排名跳号,二次分组才会准确。 - 业务扩展:可扩展到连续逾期、连续缴存、连续活跃等允许短期间断的业务场景。
总结
连续类问题的通用解题范式是**"窗口排序 → 构造分组标记 → 分组聚合"**:
- 严格连续场景:一次分组即可,
日期 - 序号是核心公式; - 允许间隔场景:多次分组递进,通过多层排名与差值计算,合并符合间隔要求的分段。 掌握这套思路,可以覆盖绝大多数连续出现、连续交易、连续打卡类 SQL 面试题。