核心思路是用ROW_NUMBER()生成行号,以“登录日期-行号”作为连续段分组依据;需确保日期为DATE类型、联合去重,并注意窗口排序方向以支持倒推连续天数。

用 ROW_NUMBER() 配合日期差计算连续段
核心思路是:对用户登录记录按日期排序,生成行号;再用登录日期减去这个行号——同一连续段内的结果必然相同。这是因为等差数列的“日期 – 行号”是定值。
常见错误是直接用 LAG() 比较前一天是否存在,逻辑易断、难以统计长度;或者用自连接暴力匹配,数据量大时性能崩盘。
- 必须确保日期字段是
DATE类型(不是DATETIME),否则DATE_SUB(login_date, INTERVAL rn DAY)会因时间部分偏差导致分组错乱 - 用户 ID 和日期需联合去重,重复打卡或同天多条记录必须先
GROUP BY user_id, DATE(login_time) - MySQL 8.0+、PostgreSQL、SQL Server 2012+ 支持
ROW_NUMBER();旧版 MySQL 只能用变量模拟,稳定性差
SELECT user_id, COUNT(*) AS days, MIN(login_date) AS start_date, MAX(login_date) AS end_date
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, DATE(login_time) AS login_date FROM login_log) t
) t2
GROUP BY user_id, grp
HAVING COUNT(*) >= 3;
识别“当前连续登录天数”要重排窗口
如果需求是“截至今天,用户最近连续登录了多少天”,就不能简单分组聚合,得从最新日期倒推,判断是否断连。
关键点在于:窗口函数的排序方向和起始边界。用 ORDER BY login_date DESC + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 才能逐条检查是否连续。
- 必须过滤掉未来日期(
login_date ),否则LEAD()或倒序窗口可能引入无效数据 - 若某用户今天没登录,但想查“如果今天登了就是第 N 天”,那得补一条虚拟今日记录再计算,不能只依赖历史
- Oracle 中
ROW_NUMBER()倒序后需配合MAX(login_date)对齐基准日,否则“连续天数”可能偏移
MySQL 5.7 怎么绕过窗口函数限制
没有 ROW_NUMBER() 就得用变量维护状态,但要注意执行顺序不可靠、并发查询可能错乱。
一站式AI应用开发和部署工具
安全写法是:子查询先排序并强制物化(加 ORDER BY ... LIMIT 9999999 或嵌套一层 FROM (SELECT ...) t),再在外层用变量累加。
- 变量初始化必须在同一个
SELECT内完成,比如@rn := 0放在SELECT列表最前,且不能依赖 WHERE 条件过滤后再赋值 - 日期比较要用
TO_DAYS()转整数,避免DATE_ADD()在跨月时出错;TO_DAYS(@prev_date) + 1 = TO_DAYS(login_date)才算连续 - 该方案无法走索引优化,10 万行以上建议升级到 MySQL 8.0 或改用应用层聚合
连续打卡场景下“节假日/周末是否计入”怎么处理
业务规则决定技术实现:如果公司要求“仅工作日打卡才算连续”,就不能只依赖原始登录表,得关联日历维表。
典型做法是预建一张 calendar_dim 表,含 date、is_workday、is_holiday 字段,JOIN 后再跑连续段逻辑。否则每次都要用复杂 CASE 判断法定假日,SQL 可读性与维护性极差。
- 别在 SQL 里硬编码节假日(如
login_date NOT IN ('2024-01-28', '2024-02-04')),一旦漏加或年份错,连续天数全错 - 如果“周末打卡也有效,但节假日无效”,需把
is_workday = 1 OR (WEEKDAY(login_date) 这类混合条件提前算成布尔字段,再参与连续分组 - 日历表必须覆盖查询时间范围前后至少 30 天,防止
DATE_SUB()计算时出现 NULL
连续问题真正的复杂点不在 SQL 写法,而在于“连续”的定义是否稳定——业务规则一变,整个分组逻辑就得重审。字段含义模糊、日期类型混用、缺失归一化清洗,比不会写窗口函数更容易导致结果错误。












