必须用窗口函数构造连续段标识,因GROUP BY丢失顺序信息;推荐LAG()+SUM()法(逻辑直白),次选ROW_NUMBER()差值法(性能优但敏感);二者均需显式唯一排序。

不能靠 GROUP BY 原始字段识别连续重复,必须用窗口函数构造“连续段标识”;最稳做法是 LAG() + SUM() 累计和,其次是 ROW_NUMBER() 差值法——二者都要求显式、唯一排序。
为什么 GROUP BY status 无法识别连续重复
因为 GROUP BY status 只会把所有相同值归为一组,完全丢失顺序信息。例如数据序列是 ['A', 'B', 'A', 'A'],GROUP BY status 统计出 A 出现 3 次,但实际最长连续段只有 2 次。连续性本质是“相邻行是否相等”,必须基于行序做判断。
- 没写
ORDER BY的窗口函数(如LAG(status))在 MySQL 8.0+ 中结果不可控,可能每次执行都不一样 - 若排序字段有重复(如多个记录同为
'2026-10-14'),必须追加唯一列,例如ORDER BY event_time, id -
GROUP BY适合统计频次,不适合刻画时序行为——这是两类问题,别混用
用 LAG() + SUM() 构造连续段标识(推荐初学者)
逻辑直白、容错性强:先标记“是否新开一段”,再用累计和生成段 ID。
- 用
LAG(status) OVER (PARTITION BY user_id ORDER BY event_time, id)拿上一行状态 - 构造标志位:
CASE WHEN status != LAG(status) OVER (...) OR LAG(status) IS NULL THEN 1 ELSE 0 END AS is_new_seg(注意OR LAG() IS NULL,否则首行被漏掉) - 用
SUM(is_new_seg) OVER (PARTITION BY user_id ORDER BY event_time, id) AS seg_id得到每段唯一编号 - 外层
GROUP BY user_id, status, seg_id后COUNT(*)就是该段长度
用 ROW_NUMBER() 差值法识别连续段(性能更优但更敏感)
适合大数据量场景,核心是两个 ROW_NUMBER() 相减,差值恒定即为同一连续段。
- 全局序号:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_time, id) - 按值分组序号:
ROW_NUMBER() OVER (PARTITION BY user_id, status ORDER BY event_time, id) - 相减得段标识:
rn_all - rn_part AS grp;MySQL 中不能直接在GROUP BY里用别名,得重复表达式或套子查询 - 若
status可能为NULL,加WHERE status IS NOT NULL,否则所有NULL被归为同一段 - 日期类字段建议先
DATE(event_time)归一化,避免同一天多条记录因时分秒不同被误拆
统计后取最长连续段时容易忽略的关键点
拿到 seg_id 或 grp 后,聚合只是第一步;真正要用结果时,边界和语义常被跳过。
- 必须同时
GROUP BY status,否则不同值的段可能因巧合共享同一seg_id(虽然概率低,但语义上必须保留) -
COUNT(*)是段内行数,不是自然日跨度;若需天数,得用MAX(event_time) - MIN(event_time) + 1配合日期函数 - 想只返回每个用户的最长段,别在外层用
LIMIT 1,而应在子查询中用QUALIFY ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) = 1(支持该语法的引擎)或嵌套聚合比较 - 首行、单元素段、空值、乱序数据都会影响结果;上线前务必用小样本验证
MIN(event_time)和MAX(event_time)是否符合业务预期

















