深度解析 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)
相关推荐
一米阳光86616 小时前
软考(中级)软件设计师核心笔记(3)数据库系统——概念、数据库设计
数据库·笔记·职场发展·软考·软件设计师
奈斯先生Vector7 小时前
告别工具碎片化:基于 Nano Banana 全模态 AI 聚合架构搭建“文本-图像-视频”自动化协同生产线
运维·数据库·人工智能·架构·自动化·aigc·音视频
建筑工程企业管理系统9 小时前
erp工程项目管理系统落地价值:实现工程多项目成本精细化核算与管控
大数据·数据库·人工智能
AI大模型-小华9 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
foolishlee10 小时前
Neon wal日志处理流程2
数据库
2601_9657984711 小时前
Is Hygia Good for Maid & Janitorial Sites? Technical Audit
服务器·网络·数据库
Cloud云卷云舒11 小时前
海山数据库(HaishanDB)面向工业云场景技术方案
数据库·haishandb·工业云·移动云海山数据库·天工云
逃逸线LOF11 小时前
Spring配置数据源{连接池}(Druid、c3p0)
java·数据库·spring
☆凡尘清心☆13 小时前
CentOS Stream 9 专用 LNMP 全自动一键脚本
linux·运维·mysql·nginx·lnmp·centos stream 9
严同学正在努力13 小时前
从备份到恢复:我用 30 分钟恢复了误删的核心业务表
android·java·数据库·ai