经典 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 面试题。
相关推荐
2601_962106641 小时前
【Java Set综合实战】商品管理系统 + 图书收藏系统|Product、Book自定义类全套代码
java
RuoyiOffice2 小时前
SpringBoot+Vue3 凭证生命周期设计:审核、过账、冲销与反过账怎样保持账目一致
java·spring boot·事务·springboot3·财务系统·会计凭证·ruoyi office
GoAI2 小时前
DeepSeek Harness Memory (一)主流记忆插件总结
后端·算法·面试
2601_962106642 小时前
HashMap入门|工号-姓名键值存储、两种遍历方式、根据key/value查
java
czq_26867194872 小时前
Python打卡第46天
开发语言·python
Pniubi2 小时前
力扣54.螺旋矩阵
算法·leetcode·矩阵
杨凯凡3 小时前
Spring Framework 7 API 版本管理实战:请求头路由、兼容迁移与废弃策略
java·rest api·springframework·spring boot 4·api版本管理
T_Wizard3 小时前
同一个问题写了 4 遍:官方 SDK / HTTP / Spring AI / LangChain4j,到底该选哪个
java·ai编程
hai_android3 小时前
AudioTrack 深度解析:从原理到实战
android·java·kotlin