MySQL 8.0+ 要求窗口函数必须显式写全 OVER 子句且 ORDER BY 不可省略;LAG/LEAD 首行返回 NULL,应使用第三参数设默认值或 CASE 显式判断;跨分组计算须加 PARTITION BY;次日留存需用 TIMESTAMPDIFF 精确判断日期差;LAG/LEAD 结果不能在 WHERE 中直接引用,须用子查询或 CTE 提前计算。

必须显式写全 OVER 子句,否则 MySQL 8.0+ 直接报错 —— 这不是警告,是语法硬限制。
为什么 LAG() 和 LEAD() 一用就报错?
最常见的错误是漏掉 ORDER BY 或完全不写 OVER。MySQL 8.0+ 要求窗口函数必须带完整 OVER,且 ORDER BY 不可省略。
-
LAG(amount) OVER (PARTITION BY user_id)→ 缺ORDER BY,报错:window function requires an OVER clause with ORDER BY -
LEAD(login_date) OVER ()→ 既无分组也无排序,被拒绝执行 - 正确结构只有这一种:
LAG(expr, offset, default) OVER (PARTITION BY col ORDER BY sort_col)
计算差值时第一行变 NULL 怎么办?
LAG() 在每个分组第一行天然返回 NULL,一旦参与运算(如 amount - LAG(amount)),结果整列为 NULL,后续 WHERE diff > 0 会丢掉所有首行。
- 安全做法:用第三参数设默认值,例如
LAG(amount, 1, 0)—— 注意类型要一致,DECIMAL字段别填'0'字符串 - 更严谨写法:显式判断
CASE WHEN LAG(amount) IS NOT NULL THEN amount - LAG(amount) END - 别依赖
COALESCE(LAG(amount), 0)做减法,它在 MySQL 中虽可用,但语义不如第三参数清晰,且跨数据库兼容性差
跨用户/跨商品计算差值,为什么结果乱了?
没加 PARTITION BY 却想“按用户算上一笔订单”,会导致张三的第二条记录取到李四的最后一条 —— 排序全局生效,不分组就等于把所有数据混在一起排。
- 查每个用户的订单金额变化:必须
PARTITION BY user_id ORDER BY created_at - 若
created_at有重复(比如并发下单),务必追加唯一字段兜底,如ORDER BY created_at, id - 时间字段优先用
DATETIME,不用DATE;DATE精度不够,排序不稳定
LEAD() 判断“次日留存”不能只看非空
仅用 LEAD(login_date) IS NOT NULL 统计的是“有后续行为的用户”,不是“次日就回来的用户”。真正留存要求时间差严格等于 1 天。
- 正确方式:
TIMESTAMPDIFF(DAY, login_date, LEAD(login_date) OVER (PARTITION BY user_id ORDER BY login_date)) = 1 - 别用
DATE_ADD(login_date, INTERVAL 1 DAY) = LEAD(login_date):1 月 31 日 +1 天 ≠ 2 月 1 日,跨月时出错 - 分母必须是新用户:先用
MIN(login_date) OVER (PARTITION BY user_id)找首次登录日,再限定该日期落在观察期内
最常被忽略的一点:你不能在 WHERE 里直接写 LAG() 或 LEAD() 的结果。它们属于窗口计算阶段,WHERE 是逻辑查询处理的早期阶段。必须用子查询或 CTE 提前算好,再过滤。


















