连续状态识别需按时间排序后用窗口函数标记断点:先用ROW_NUMBER()全局排序减去按状态局部排序的差值生成分组标识grp,相同grp即为连续段;LAG()虽可简化单行比对,但稳定性不如差值法,且必须确保ORDER BY含唯一列防重复时间错位。

连续状态相同记录的识别逻辑
“连续”不是指物理存储顺序,而是按时间或序号字段排序后,相邻行的状态值相同。SQL 本身不保存行序,所以必须显式用 ORDER BY 定义顺序(比如 event_time 或 id),再通过窗口函数标记“是否断开”。常见错误是直接 GROUP BY status——这会把所有相同状态全归一组,完全忽略“连续”这个前提。
用 ROW_NUMBER() 构造分组标识
核心思路是:对全表按时间排序,再分别对 status 做局部排序,两者相减得到稳定分组号。只要差值相同,就属于同一段连续区间。
- MySQL / PostgreSQL / SQL Server 都支持该写法(需 8.0+ / 9.4+ / 2005+)
- 示例字段假设为:
id(递增主键)、status(VARCHAR)、event_time(DATETIME) - 关键子查询:
SELECT status,
MIN(event_time) AS start_time,
MAX(event_time) AS end_time
FROM (
SELECT *,
ROW_NUMBER() OVER (ORDER BY event_time)
- ROW_NUMBER() OVER (PARTITION BY status ORDER BY event_time) AS grp
FROM events
) t
GROUP BY status, grp;注意:grp 本身无业务含义,仅用于区分连续块;MIN/MAX 取的是每段首尾时间,不是整个状态的所有时间。
MySQL 8.0+ 可用 LAG 简化判断
如果只关心“当前行状态是否与上一行相同”,LAG() 更直观,但需两层嵌套才能聚合:
- 第一层加标记:
LAG(status) OVER (ORDER BY event_time) - 第二层用变量或条件生成分组号(如
CASE WHEN status = prev_status THEN @grp ELSE @grp := @grp + 1 END) - 实际中不如
ROW_NUMBER()差值法稳定,尤其在并发插入或时间重复时容易出错
真正容易被忽略的是边界情况:时间相同但 id 不同的记录,是否算“连续”?这取决于业务定义——若允许时间重复,必须把 ORDER BY 写成 ORDER BY event_time, id,否则窗口函数结果不可靠。

















