LAG(value) - value 返回NULL是因为首行无前驱值,LAG默认返回NULL,参与运算即得NULL;须用CASE或COALESCE处理首行,并严格按业务排序、分组、防除零,否则突变判断不可信。

能直接定位突变行,但必须处理好排序、NULL 和业务定义——漏掉任意一项,结果就不可信。
为什么 LAG(value) - value 会返回一堆 NULL
第一行没有“前一行”,LAG(value) 默认返回 NULL;一旦参与减法(如 value - LAG(value)),整条结果就是 NULL。这不是报错,是预期行为。
- 用
COALESCE(LAG(value), value)把首行差值设为 0(即自身减自身) - 或更清晰地写:
CASE WHEN LAG(value) IS NULL THEN 0 ELSE value - LAG(value) END - 别试图在
WHERE里过滤LAG() IS NOT NULL——窗口函数不能出现在WHERE子句中
突变判断不能只看差值绝对值,得结合业务场景
单纯 ABS(value - LAG(value)) > 100 很危险:金额从 100 到 200 是+100,但从 10000 到 10100 也是+100,后者未必算“突变”。
- 用相对变化率更稳妥:
ABS((value - LAG(value)) / NULLIF(LAG(value), 0)) > 0.3(加NULLIF防除零) - 时间序列中,突变常需排除首行和节假日断点,建议先用
ROW_NUMBER() OVER (ORDER BY ts)标记序号,再限定rn > 1 - 若数据按用户/设备分组波动,必须加
PARTITION BY user_id,否则 A 用户末尾和 B 用户开头会被错误拉通计算
ORDER BY 不写或写错,突变结果全是假的
LAG() 完全不认表的插入顺序或主键顺序,只认 OVER (ORDER BY ...) 里写的字段。排序字段一旦有重复(比如多条记录同秒写入),数据库可能任意打乱它们的相对位置。
- 必须用带业务意义的字段排序,如
created_at、event_time,而非id(除非你确认 id 严格递增且无删改) - 存在时间精度问题时(如 MySQL 的
DATETIME只到秒级),务必补二级排序:ORDER BY created_at, id - MySQL 5.7 及更早版本不支持窗口函数,强行执行会报
ERROR 1064;此时只能用变量模拟或应用层处理,但变量方式在并发或大结果集下极易出错
怎么把突变行连同上下文一起查出来
只标出“哪一行突变了”不够,常需查看突变前后的原始值、时间、关联 ID 等上下文信息。
- 直接在 SELECT 中展开:
LAG(value) OVER (ORDER BY ts) AS prev_value、LEAD(value) OVER (ORDER BY ts) AS next_value - 避免多次调用
LAG()计算同一列,性能无额外开销,但可读性更强 - 如果要高亮突变点,可用布尔表达式:
(value - LAG(value)) > 500 AS is_spike,再在外层WHERE is_spike过滤(注意:需用子查询或 CTE,因别名不能在同级 WHERE 用)
真正难的不是写出 LAG(),而是确认排序逻辑是否覆盖所有业务边界——比如跨天、跨区、时区转换、批量导入导致的时间戳乱序。这些地方一松懈,突变检测就变成随机抽查。

















