必须用lag()/lead()配合完整over子句,含partition by和order by;首行null需用case显式处理;排序键须唯一以防结果不可复现。

直接用 LAG() 或 LEAD() 配合 OVER 子句,别写自连接或子查询——那是低效且易错的老办法。
必须写全 OVER 子句,否则语法报错
SQL Server 强制要求 LAG() 和 LEAD() 后面跟 OVER(),缺 ORDER BY 就会报 ERROR: window function requires an OVER clause。只写 PARTITION BY 不够,没排序就无法定义“上一行”。
-
LAG(amount) OVER (ORDER BY created_at)—— 基础写法,按时间取前一笔金额 -
LAG(amount) OVER (PARTITION BY user_id ORDER BY created_at, id)—— 分组+排序,末尾加id防止时间重复导致顺序不确定 - 别写成
LAG(amount) OVER (PARTITION BY user_id),SQL Server 2016+ 会直接拒绝执行
差值计算时 NULL 怎么处理才不崩
LAG() 对第一行返回 NULL,直接做减法(如 amount - LAG(amount))结果也是 NULL,整列变空。这不是 bug,是设计使然,但业务通常要规避。
- 用
COALESCE(LAG(amount), 0)把首行差值设为amount,但注意:这在金额类场景可能误报(比如首单 100 元,差值变成 100,其实并无“变化”) - 更合理的是显式过滤:
CASE WHEN LAG(amount) OVER (...) IS NOT NULL THEN amount - LAG(amount) OVER (...) END - 别依赖第三个参数填默认值(如
LAG(amount, 1, 0)),SQL Server 2016 及更早版本不支持该语法,仅 SQL Server 2022+ 才可用
分组内比较漏掉 PARTITION BY 是高频翻车点
想查“每个用户订单金额是否比上一笔高”,如果只按时间全局排序,LAG() 会把用户 A 的最后一单和用户 B 的第一单强行拉一起算差值——逻辑完全错乱。
- 必须加
PARTITION BY user_id,确保窗口只在同用户数据内滑动 - 排序字段类型要可靠:
updated_at比updated_time_str(字符串格式时间)安全得多,后者可能因格式不统一导致隐式转换和排序错位 - 索引建议:在
(user_id, updated_at, id)上建复合索引,能显著加速OVER (PARTITION BY user_id ORDER BY updated_at, id)的执行
真正容易被忽略的不是函数怎么写,而是排序的稳定性——哪怕加了 PARTITION BY 和 ORDER BY,只要排序键存在重复且未补二级键,SQL Server 就可能每次返回不同“上一行”,差值结果不可复现。










