




核心思路是用ROW_NUMBER()生成行号,以“登录日期-行号”作为连续段分组依据;需确保日期为DATE类型、联合去重,并注意窗口排序方向以支持倒推连续天数。
核心思路是:对用户登录记录按日期排序,生成行号;再用登录日期减去这个行号——同一连续段内的结果必然相同。这是因为等差数列的“日期 - 行号”是定值。
常见错误是直接用 LAG() 比较前一天是否存在,逻辑易断、难以统计长度;或者用自连接暴力匹配,数据量大时性能崩盘。
DATE 类型(不是 DATETIME),否则 DATE_SUB(login_date, INTERVAL rn DAY) 会因时间部分偏差导致分组错乱GROUP BY user_id, DATE(login_time)
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() 或倒序窗口可能引入无效数据
ROW_NUMBER() 倒序后需配合 MAX(login_date) 对齐基准日,否则“连续天数”可能偏移没有 ROW_NUMBER() 就得用变量维护状态,但要注意执行顺序不可靠、并发查询可能错乱。
安全写法是:子查询先排序并强制物化(加 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) 才算连续业务规则决定技术实现:如果公司要求“仅工作日打卡才算连续”,就不能只依赖原始登录表,得关联日历维表。
典型做法是预建一张 calendar_dim 表,含 date、is_workday、is_holiday 字段,JOIN 后再跑连续段逻辑。否则每次都要用复杂 CASE 判断法定假日,SQL 可读性与维护性极差。
login_date NOT IN ('2025-01-28', '2025-02-04')),一旦漏加或年份错,连续天数全错is_workday = 1 OR (WEEKDAY(login_date) 这类混合条件提前算成布尔字段,再参与连续分组
DATE_SUB() 计算时出现 NULL连续问题真正的复杂点不在 SQL 写法,而在于“连续”的定义是否稳定——业务规则一变,整个分组逻辑就得重审。字段含义模糊、日期类型混用、缺失归一化清洗,比不会写窗口函数更容易导致结果错误。