深度解析 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)
相关推荐
承渊政道1 小时前
【MySQL数据库学习】MySQL表的约束(上)
数据库·c++·学习·mysql·bash·数据库架构·数据库系统
星越华夏1 小时前
SQLite数据库优化实战技巧案例
数据库·sqlite
梓䈑1 小时前
【MySQL】一文梳理MySQL 8.0常用数据类型:含存储范围、对比差异与实操案例
数据库·mysql
l1t1 小时前
DeepSeek总结的PostgreSQL 19 中的 SQL/PGQ:无需图数据库的图查询
数据库·sql·postgresql
j7~1 小时前
【MYSQL】用户管理--详解
数据库·mysql·用户管理·数据库权限·mysql修改用户密码
倔强的石头1061 小时前
《Kingbase护城河》——跨平台环境下的数据库联调实战
数据库
曹牧9 小时前
Oracle:前缀匹配之REGEXP_LIKE
数据库·oracle
暴躁小师兄数据学院12 小时前
【AI大数据工程师特训笔记】第05讲:关联查询
数据库·sql·oracle
倔强的石头_12 小时前
《Kingbase护城河》——跨平台环境下的数据库联调实战
数据库