“连续相同状态”指按时间排序后相邻行status值相同且未被其他状态打断的序列,需用差值法(row_number()两层相减)生成分组标签grp再聚合,而非直接group by status。

什么是“连续相同状态”?先看典型错误现象
直接用 GROUP BY status 会把所有相同状态的行全压成一条,比如“登录→登出→登录”会被合并成两条——但实际业务里,“登录”中间断开了,不该算连续。真正的连续是指:按时间排序后,相邻行的 status 值相同,且中间没被其他状态打断。
核心解法:用 LAG() 或差值法识别连续段
关键不是分组本身,而是先给每段连续状态打上唯一标签(grp),再按这个标签聚合。
-
LAG(status) OVER (PARTITION BY user_id ORDER BY event_time)可比当前行和上一行状态是否一致,但只能判断“是否变化”,不能直接生成分组ID - 更稳的做法是:构造一个随“状态切换”而递增的序列,常用差值法:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_time) - ROW_NUMBER() OVER (PARTITION BY user_id, status ORDER BY event_time)
这个差值在连续相同status区间内恒定,一变就跳变,天然形成grp - PostgreSQL/MySQL 8.0+/SQL Server 都支持;MySQL 5.7 不支持窗口函数,得用变量模拟(易出错,不推荐)
合并时保留首尾时间、持续时长等关键信息
仅用 MIN(event_time) 和 MAX(event_time) 算起止时间最常见,但要注意:
- 如果原始时间是
TIMESTAMP类型,直接相减可能得秒数(MySQL)或 interval(PostgreSQL),需用TIMEDIFF()/AGE()/DATEDIFF()转换 - 想统计“连续登录了几天”,别直接用
COUNT(*)——那是记录条数,应取DATEDIFF(MAX(event_time), MIN(event_time)) + 1 - 若状态含“进行中”类未结束项,
MAX(event_time)可能失真,建议加WHERE status != 'pending'过滤
容易漏掉的边界情况:用户跨天、空状态、多字段联合判断
真实日志里,status 字段可能为空、为 NULL,或需结合 user_id + device_id 才算真正连续——这时 PARTITION BY 必须同步扩展:
-
PARTITION BY user_id, device_id ORDER BY event_time,否则同一用户不同设备的登录会串在一起 - NULL 状态默认不参与
LAG()比较,会导致误判连续,建议提前用COALESCE(status, 'unknown')统一处理 - 时间精度问题:毫秒级时间戳若没去重,可能因微小差异把本该连续的两行拆开,可先用
DATE(event_time)或FLOOR(UNIX_TIMESTAMP(event_time)/60)对齐到分钟级
连续状态合并的本质是“找断点”,不是“找重复”。只要断点识别准了,后面聚合就是体力活。最常翻车的地方,永远在 PARTITION BY 漏字段、时间排序没加 ORDER BY、或者 NULL 处理不到位。










