深度解析 SQL 经典面试题:如何优雅地计算连续登录天数?

问题描述:

数据表字段:user_id,login_date,一天多次登录只算当天登录一次。

问题需求1:

统计每个用户的总登陆天数

sql 复制代码
SELECT 
    user_id,
    COUNT(DISTINCT login_date) AS total_login_days
FROM 
    your_table_name  -- 替换成你实际的表名
GROUP BY 
    user_id;

问题需求2:

统计每个用户的连续登陆天数
思路:

假设有个用户连续三天登录:

  • 1号(序号是1):用 1号 减去 1天,结果是 0号
  • 2号(序号是2):用 2号 减去 2天,结果还是 0号
  • 3号(序号是3):用 3号 减去 3天,结果依然是 0号

你看,只要是连续的,算出来的结果(也就是那个"差值")永远都是同一个数(比如都是0号)。

那如果中间断了一天呢?比如跳到了5号:

  • 5号(序号变成了4):用 5号 减去 4天,结果就变成了 1号

结果从"0号"变成了"1号",不一样了!这就说明中间断开了,属于新的一段连续登录。

简单总结一下就是:

我们在 SQL 里算出这个"差值",然后按这个差值分组。只要差值一样,就说明这几天是连着的;差值变了,就说明中间断过。

这样解释是不是清楚多啦?😄

💡 代码逻辑拆解

  1. 去重 (DISTINCT):先把同一天内的多次登录合并成一条,避免干扰后续的序号排列。
  2. 打标 (ROW_NUMBER + DATE_SUB)
    • ROW_NUMBER() 会为每个用户的登录记录按时间先后生成 1, 2, 3... 的序号。
    • 假设某用户连续在 01-01, 01-02, 01-03 登录,序号分别是 1, 2, 3。
    • 用日期减去序号(01-01 减 1天 = 12-3101-02 减 2天 = 12-31...),你会发现算出来的 grp 都是同一天(12-31)。这个 grp 就是该段连续登录的"唯一锚点"。
  3. 分组统计 (GROUP BY) :只要 grp 相同,就说明它们在同一段连续登录区间里。直接按 user_idgrp 分组,COUNT(*) 就能算出这段连续了多少天。
  4. 筛选 (HAVING) :最后可以根据业务需求,比如筛选出 COUNT(*) >= 3 的"忠实用户"。
sql 复制代码
SELECT 
    user_id,
    MIN(login_date) AS start_date,      -- 这段连续登录的开始日期
    MAX(login_date) AS end_date,        -- 这段连续登录的结束日期
    COUNT(*) AS consecutive_days        -- 连续登录的天数
FROM (
    -- 第二步:计算"差值日期"(打上连续区间的标签)
    SELECT 
        user_id,
        login_date,
        -- 核心逻辑:用登录日期 减去 它在组内的排名序号
        DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY) AS grp
    FROM (
        -- 第一步:数据去重(同一天多次登录只保留一条)
        SELECT DISTINCT user_id, login_date
        FROM user_login
    ) t1
) t2
GROUP BY user_id, grp  -- 第三步:按用户和差值日期分组
HAVING COUNT(*) >= 3;  -- 第四步:筛选(比如找出连续登录3天及以上的记录)

问题需求3:

统计每个用户的最长连续登陆天数

如果你不需要列出每一段连续记录,只想知道每个用户历史上最长连续登了多少天 ,只需要在上面的基础上再套一层 MAX 聚合:

sql 复制代码
SELECT 
    user_id,
    MAX(consecutive_days) AS max_consecutive_days  -- 取出该用户所有连续段中的最大值
FROM (
    SELECT 
        user_id,
        COUNT(*) AS consecutive_days
    FROM (
        SELECT 
            user_id,
            DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY) AS grp
        FROM (
            SELECT DISTINCT user_id, login_date
            FROM user_login
        ) t1
    ) t2
    GROUP BY user_id, grp
) t3
GROUP BY user_id;

问题需求4:

想看"连续登录天数最长"的用户

sql 复制代码
SELECT 
    user_id,
    MAX(consecutive_days) AS max_consecutive_days  -- 算出每个用户的最长连续天数
FROM (
    SELECT 
        user_id,
        COUNT(*) AS consecutive_days
    FROM (
        SELECT 
            user_id,
            -- 核心:日期减去序号,得出连续分组的基准日期
            DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY) AS grp
        FROM (
            SELECT DISTINCT user_id, login_date
            FROM user_login
        ) t1
    ) t2
    GROUP BY user_id, grp
) t3
GROUP BY user_id
ORDER BY max_consecutive_days DESC  -- 按最长天数降序排列
LIMIT 1;  -- 只取第1名(如果想看前10名,就改成 LIMIT 10)
相关推荐
Jim6001 天前
【吃透 MySQL InnoDB连载】第 1 章・解密线上数据库高频故障
mysql
GreatSQL1 天前
gt-checksum v4.0.0 新功能解读系列文章(4):SSL 加密连接——数据校验传输安全再升级
mysql
倔强的石头_1 天前
KingbaseES 新版MySQL 兼容版体验:旧版迁移 + 功能实测
数据库
zzzzzz3102 天前
9K Star 炸裂开源!这个 C 语言写的代码知识图谱,把 Linux 内核索引压缩到了 3 分钟
linux·服务器·sql
倔强的石头_4 天前
《Kingbase护城河》——数据库存储空间全景探测与精细化瘦身实战
数据库
云技纵横4 天前
唯一索引 INSERT 死锁实战:5 秒复现交叉插入的 S 锁循环等待
sql·mysql
沉默王二4 天前
面试官:RAG 不用向量数据库,用 MySQL 硬扛?我:100 万向量不是很轻松?
mysql·面试·ai编程
冬奇Lab5 天前
每日一个开源项目(第134篇):Zvec - 阿里开源的嵌入式向量数据库,向量搜索界的 SQLite
数据库·人工智能·llm
小猿姐5 天前
MySQL Top 10 热点问题 AI 运维实战:从内核诊断到云原生运维
mysql·云原生·aiops
ClouGence5 天前
Oracle CDC 架构优化:从主库直连到 DataGuard 备库同步
数据库·后端·oracle