经典 SQL 面试题:连续登录问题的两种经典解法

连续登录(连续交易、连续打卡、连续逾期)是 SQL 面试的高频经典题型,核心考察窗口函数与分组聚合的组合运用,本质是通过构造统一分组标记 ,将连续的记录归为同一组,再做聚合统计。常见分为两个核心变形:严格连续登录次数统计 、允许间隔 1 天的连续天数统计,下面分别拆解思路与实现。


5.1 连续登录次数最大的用户(严格连续)

题目描述

给定用户登录表,每条记录代表一次登录行为,找出连续登录次数最多的用户。连续定义为:登录日期严格连续,中间没有任何中断。

核心思路:日期 - 序号 分组法

这是严格连续问题的标准解法,核心逻辑: 对同一用户的登录日期按升序依次编号,连续的日期减去递增的序号,得到的差值必然完全相同。将 "用户 + 差值" 作为分组键,每组内的记录就是一段完整的连续登录,统计每组长度即可得到连续次数。

分步拆解

  1. 窗口排序编号 按用户分区,登录日期升序排列,用 ROW_NUMBER() 生成每个用户内部的连续序号。
  2. 构造分组标记 用登录日期减去对应序号,得到差值 sn1。同一段连续登录的日期,减去对应序号后结果完全一致。
  3. 分组统计次数 按 用户 + 差值sn1 分组,统计每组的登录次数。
  4. 排序取最大值 按次数降序排序,取排名第一的记录,即为连续登录次数最多的用户。

完整 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) 实现日期减序号。

易错点与扩展

  1. 单日去重 :若用户同一天多次登录,需先通过 DISTINCT id,dt 去重,避免序号膨胀导致分组错误。
  2. 并列第一处理 :ROWNUM=1 仅取一条结果;若需返回所有并列最多的用户,改用 RANK() OVER(ORDER BY login_cnt DESC)=1。
  3. 场景复用:该方法可直接扩展到连续交易、连续打卡、连续逾期等所有严格连续场景。

5.2 最大连续登录天数(允许间隔一天)

题目描述

允许登录中间间隔 1 天,仍视为连续。例如:1 月 1 日登录、1 月 2 日未登录、1 月 3 日登录,认定为连续登录 3 天。求每个用户的最大连续登录天数。

核心思路:双层分组法

相比严格连续,允许间隔 1 天的场景需要两次分组递进:

  1. 先按严格连续规则,把无中断的登录归为多个小段;
  2. 再对这些小段做二次分组,把间隔 1 天的小段合并为一个大段,最终计算大段的天数跨度。 核心技巧:用 DENSE_RANK() 给严格连续段编号,再用日期减去排名,间隔 1 天的段会得到相同的分组标记,自动合并。

分步拆解

  1. 第一层:严格连续分段 同 5.1 逻辑,用 日期 - ROW_NUMBER() 得到 sub1,将严格连续的登录归为一组。
  2. 给连续段排名 按用户分区,对 sub1 升序做 DENSE_RANK() 得到 rk,每个严格连续段对应一个固定排名。
  3. 第二层:构造合并分组标记 用原始日期减去 rk 天,得到 sub2。间隔 1 天的两个严格连续段,减去对应排名后,差值会相同,从而被归为同一大组。
  4. 计算连续天数 按 用户 + sub2 分组,用 最大日期 - 最小日期 + 1 计算该段连续天数。
  5. 取用户最大值 按用户分组,取最大的连续天数。

完整 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. 间隔定义澄清:本题 "允许间隔一天" 指中间空 1 天(日期差 2 天)仍算连续;若允许间隔 N 天,只需调整二次分组的偏移量。
  2. 为什么用 DENSE_RANK 不用 ROW_NUMBER :DENSE_RANK 保证每个严格连续段排名唯一且连续,避免间隔导致排名跳号,二次分组才会准确。
  3. 业务扩展:可扩展到连续逾期、连续缴存、连续活跃等允许短期间断的业务场景。

总结

连续类问题的通用解题范式是**"窗口排序 → 构造分组标记 → 分组聚合"**:

  • 严格连续场景:一次分组即可,日期 - 序号 是核心公式;
  • 允许间隔场景:多次分组递进,通过多层排名与差值计算,合并符合间隔要求的分段。 掌握这套思路,可以覆盖绝大多数连续出现、连续交易、连续打卡类 SQL 面试题。
相关推荐
weixin_BYSJ19872 小时前
【计算机毕设】房屋租赁系统02647
java·vue.js·spring boot·python·django·课程设计
大熊背2 小时前
一种数码相机快速自动曝光控制方法解读
算法·计算机视觉·自动曝光·快速收敛
VX_bysjlw9852 小时前
基于协同过滤的美食推荐系统的设计与实现32221-计算机毕设原创(免费领源码+带部署教程)
java·spring boot·redis·mysql·tomcat·maven·mybatis
程序猿阿森2 小时前
深入解析 NLI:RAG 与大模型防幻觉的“最后一道防线”
前端·人工智能·算法
星夜离尘562 小时前
MATLAB 只剩批次摘要怎么合并方差:用 n、mean、M2 保留批间差异
开发语言·matlab
Misnice2 小时前
Python 的数据类型
java·windows·python