不能用聚合函数计算前后行差值,必须用LAG/LEAD获取原始值后手动相减;因SUM等返回窗口和而非差值,语义错误、无法定位异常、性能差且边界行为不可控。

直接结论:不能用聚合函数(如SUM、AVG)在窗口内做“前后行差值”,必须用LAG/LEAD拿到相邻行原始值再手动相减;否则逻辑错、结果空、性能崩。
为什么不能写 SUM(x) OVER (ORDER BY t ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) 算前后差?
这种写法看似“覆盖前后行”,但SUM算的是窗口内三行的总和,不是“当前行减上一行”。你想得到的差分量(比如销量突降、响应时间跳变),本质是两个标量相减,不是聚合统计。强行套聚合窗口:
- 结果语义错误:返回的是和,不是差
- 无法定位异常点:你没法从一个总和里反推哪一行出问题
- 性能浪费:SUM要扫描整个窗口,而LAG只需取单个偏移值
- 首尾行为不可控:窗口边界处SUM可能少于3行,导致结果不一致
LAG() + 手动相减才是正解,但排序和NULL处理必须显式声明
业务异常点(如某小时订单量比前一小时暴跌50%)依赖严格的时间或业务序号顺序。窗口函数不保证物理顺序,ORDER BY缺一不可:
- 必须写
LAG(value) OVER (ORDER BY event_time),不能只靠表默认顺序 - 首行的
LAG(value)恒为NULL,直接参与减法会令整行结果变NULL—— 要用COALESCE(LAG(value), value)或明确过滤首行 - 若时间精度到秒但存在重复时间戳,需加次级排序,例如
ORDER BY event_time, id,否则LAG结果不确定
差值判断时,浮点误差和边界值容易漏判
比如检测“响应时间增长超200ms”,写 WHERE diff > 200 看似没问题,但实际常踩坑:
- 原始字段是
DECIMAL(10,3)或FLOAT,相减后可能有微小浮点误差(如 200.0000000001),建议先ROUND(diff, 1)再比较 - 差值为 0 时(持平)常被忽略,若业务需区分“无变化”和“未定义”,必须显式写
CASE WHEN diff = 0 THEN 'no_change',否则进ELSE分支可能误标 - 别在主查询用
WHERE diff > 0—— 窗口函数不能出现在WHERE中,必须包一层子查询或CTE
真实异常检测SQL骨架(可直接改字段名复用)
SELECT
event_time,
value,
COALESCE(value - LAG(value) OVER (ORDER BY event_time), 0) AS diff,
CASE
WHEN ROUND(value - LAG(value) OVER (ORDER BY event_time), 1) > 200 THEN 'spike'
WHEN ROUND(value - LAG(value) OVER (ORDER BY event_time), 1) = '2026-09-25'
注意:最后一行 WHERE 是对原始数据过滤,不是对 diff 过滤;所有基于窗口结果的判断,都必须在外部再套一层查询才能加 WHERE anomaly_type = 'drop'。
最易被忽略的一点:LAG 的排序字段一旦有重复值且没加唯一二级排序,不同执行计划下结果可能不一致 —— 这在排查线上偶发异常时最难复现。










