时间片切割本质是动态区间识别,需先定义边界条件(如间隔>300秒则开启新片),再用lag()取前值、case标记断点、sum() over累计生成片id,依赖order by字段索引保障性能。

时间片切割的本质是分组 + 排序 + 边界识别
直接用 GROUP BY 按小时/分钟分组只能做静态切片,而真实业务中常需“连续 5 分钟内有数据就合并为一片”——这本质是动态区间识别问题。窗口函数不是万能胶,但 LAG()、ROW_NUMBER() 和 MAX() OVER 组合能解决绝大多数场景。
关键不是选哪个函数,而是先定义“片”的边界条件:比如“与前一条记录间隔 > 300 秒,则开启新片”。这个判断必须放在 WHERE 或子查询里完成,不能靠 GROUP BY 硬凑。
- 先用
LAG(event_time) OVER (ORDER BY event_time)取出上一行时间 - 用
CASE WHEN event_time - LAG(...) > INTERVAL '300' SECOND THEN 1 ELSE 0 END标记断点 - 再用
SUM(断点标记) OVER (ORDER BY event_time)生成连续片 ID
用 SUM() OVER 做累计分组标识最稳
ROW_NUMBER() OVER (...) % N 看似能按固定长度切片,但它只看排序位置,不看时间值本身——如果某段缺数据,% 切出来的“5 分钟片”实际可能跨 20 分钟。真正可靠的是基于时间差的累计标识。
示例:给每条记录打上所属“连续活跃片”的编号:
SELECT *,
SUM(is_new_slice) OVER (ORDER BY event_time ROWS UNBOUNDED PRECEDING) AS slice_id
FROM (
SELECT *,
CASE WHEN event_time - LAG(event_time) OVER (ORDER BY event_time) > INTERVAL '300' SECOND
THEN 1 ELSE 0 END AS is_new_slice
FROM events
) t
注意 ROWS UNBOUNDED PRECEDING 是必须写的,否则在某些数据库(如 PostgreSQL)里默认是 RANGE,会导致相同时间戳的记录被错误聚合。
MySQL 8.0+ 和 PostgreSQL 的 INTERVAL 写法差异
时间差计算最容易翻车的地方是单位写错或类型不匹配。MySQL 要求显式转成秒再比较,PostgreSQL 支持直接用 INTERVAL 字面量。
- MySQL:用
TIMESTAMPDIFF(SECOND, LAG(event_time) OVER (...), event_time) > 300 - PostgreSQL:用
event_time - LAG(event_time) OVER (...) > INTERVAL '300' SECOND - 注意
LAG()返回 NULL 的第一行,必须用COALESCE(LAG(...), event_time)或WHERE过滤掉,否则整个表达式结果为 NULL
性能陷阱:ORDER BY 和索引强绑定
窗口函数的 ORDER BY 不是装饰,它决定执行计划是否走索引。如果 ORDER BY event_time 对应字段没索引,大表上会触发全表排序,比加个 GROUP BY 还慢。
实操建议:
- 确保
ORDER BY的字段有单列索引,或至少是复合索引的最左前缀 - 避免在
OVER子句里写ORDER BY ABS(x)这类无法利用索引的表达式 - 测试时用
EXPLAIN ANALYZE(PostgreSQL)或EXPLAIN FORMAT=TREE(MySQL 8.0+)确认是否走了索引扫描
时间片切割真正难的从来不是语法,而是把“业务定义的片”准确翻译成可计算的断点条件——写完记得拿几条真实数据手算一遍 slice_id,比跑十遍 SQL 更管用。











