深度解析 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)
相关推荐
山甫aa30 分钟前
JavaWeb后端开发学习手册
java·开发语言·数据库·学习·mysql·springboot·web
JavaPub-rodert40 分钟前
MongoDB 常用命令和常见问题大全
数据库·mongodb
Dovis(誓平步青云)2 小时前
从Redis指标采集到异常告警:redis_exporter + Prometheus 完整实战
服务器·数据库·人工智能·redis·架构·prometheus·vibe coding
城管不管10 小时前
重生——第十一次面试之挖财一面2026.8.19已OC
java·服务器·jvm·数据库·spring·面试·职场和发展
倔强的石头10610 小时前
向量数据库从相似度检索走向融合数据底座
数据库
今天AI了吗11 小时前
Python 基础语法(一):常量、变量、输入输出与运算符
开发语言·数据库·人工智能·python·sql·深度学习·机器学习
Csvn12 小时前
📊 SQL 入门 Day 22:视图与物化视图
后端·sql
夜雪一千12 小时前
MySQL 全局锁是什么?原理、风险、备份踩坑完整实战
android·mysql·adb
lilian23312 小时前
Harmony os 技术实战|拼豆制图27:用单字符编码承载 50 张 70×70 图纸
前端·数据库·华为·harmonyos
皮卡丘不断更13 小时前
手机优先的 Personal Ledger:把账目、学习和复盘放到同一个入口
数据库·sqlite·fastapi·开源项目·个人效率