结论:lag()和lead()必须配合完整over子句使用,否则报错或结果错误;需确保partition by、唯一order by及null安全处理。

直接说结论:用 LAG() 计算“当前行减上一行”,用 LEAD() 计算“下一行减当前行”,但必须写全 OVER (PARTITION BY ... ORDER BY ...),否则要么报错,要么结果错乱。
LAG() 和 LEAD() 报错 “window function requires an OVER clause” 怎么办
这是最常卡住新手的第一步。这两个函数不是普通标量函数,不能脱离窗口上下文单独使用。
- 错误写法:
SELECT id, amount, LAG(amount) FROM sales;→ 必报ERROR: window function requires an OVER clause - 正确写法必须带
OVER,且ORDER BY不可省:例如LAG(amount) OVER (ORDER BY sale_date) - 如果漏掉
PARTITION BY却想分组计算(比如每个用户独立看),结果会跨用户拉数据——第一笔张三订单的“上一笔”,可能取到李四最后一笔,逻辑完全崩坏
分组内计算差值时,ORDER BY 字段重复导致结果不稳定
即使写了 PARTITION BY user_id ORDER BY event_time,若多条记录 event_time 相同(比如秒级时间戳、日志批量写入),MySQL 8.0+ 无法确定“哪一行是上一行”,LAG() 返回值可能每次查询都不一样。
- 解决方案:在
ORDER BY中追加唯一字段,如ORDER BY event_time, id或ORDER BY event_time, create_at DESC - 不推荐仅靠
ORDER BY event_time+ROW_NUMBER()二次排序,徒增复杂度 - 生产环境建议给
(user_id, event_time, id)建联合索引,既加速排序,也避免临时文件排序
计算差值时 NULL 处理不当,整列变空
LAG() 在分组首行、LEAD() 在分组末行默认返回 NULL。一旦参与运算(如 amount - LAG(amount)),结果直接为 NULL,后续 WHERE diff > 0 就过滤掉所有首/末行。
- 用第三参数设安全默认值:例如
LAG(amount, 1, 0) OVER (...),让首行差值 =amount - 0 - 更严谨的做法是显式过滤:
CASE WHEN LAG(amount) OVER (...) IS NOT NULL THEN amount - LAG(amount) OVER (...) END - 注意类型一致:若
amount是DECIMAL,别用字符串'0'当默认值,否则隐式转换可能报错或截断
为什么不能在 WHERE 中直接过滤 LAG() 结果
SQL 执行顺序决定:WHERE → GROUP BY → HAVING → SELECT → 窗口函数计算 → ORDER BY。窗口函数在 WHERE 之后才执行,所以 WHERE LAG(amount) > 100 语法就非法。
- 正确做法:用子查询或 CTE 包一层,例如
SELECT * FROM (SELECT ..., LAG(amount) OVER (...) AS prev_amt FROM t) t2 WHERE t2.prev_amt > 100 - 别为了省一层嵌套强行在
SELECT里用CASE模拟过滤——可读性差,且无法利用索引 - 如果只是想排除首行,更高效的是在外部加
WHERE row_num > 1(配合ROW_NUMBER() OVER (...))
真正难的不是写对语法,而是想清楚“分组边界在哪”“排序依据是否唯一”“NULL 是业务含义还是计算噪声”。这三个点没理清,再短的 LAG() 语句也会产出不可信的差值。











