lead()需配合order by才能正确获取下一行值,否则行为未定义;必须用子查询或cte封装后过滤,且需判断delta is not null以避免null干扰。

LEAD 函数怎么写才能拿到下一行的值
直接用 LEAD() 就行,但必须配合 ORDER BY —— 没有明确排序,LEAD() 返回的是未定义行为(多数数据库返回 NULL 或乱序值)。常见错误是只写 GROUP BY 却忘了 ORDER BY,结果突变点全标错了。
示例:按时间查用户余额变化,想看相邻两条记录的差值:
SELECT user_id, log_time, balance, LEAD(balance) OVER (PARTITION BY user_id ORDER BY log_time) AS next_balance FROM user_balance_log;
-
PARTITION BY user_id确保只在同用户内比较,不跨人混算 -
ORDER BY log_time是强制项,时间字段不能有 NULL,否则LEAD()可能跳过或错位 - 如果 log_time 精度到秒但存在并发写入,建议加二级排序,比如
ORDER BY log_time, id
怎么用 LEAD 和当前行做差并判断是否超阈值
不能把 LEAD() 放在 WHERE 里——窗口函数不能直接过滤,得包一层子查询或用 CTE。否则会报错 invalid use of window function。
突变阈值设为 ±500 元,只保留跳变绝对值超限的记录:
WITH diffed AS (
SELECT
user_id,
log_time,
balance,
LEAD(balance) OVER (PARTITION BY user_id ORDER BY log_time) AS next_balance,
LEAD(balance) OVER (PARTITION BY user_id ORDER BY log_time) - balance AS delta
FROM user_balance_log
)
SELECT * FROM diffed
WHERE delta IS NOT NULL AND ABS(delta) > 500;
-
delta IS NOT NULL必须加,否则末尾记录的LEAD()返回 NULL,ABS(NULL)还是 NULL,被 WHERE 过滤掉——这反而是对的,但漏判常源于忘了这个条件 - 阈值用
ABS()包一层,避免分别写正负两个条件 - 如果业务只关心“突然增加”,就去掉
ABS(),直接delta > 500
LEAD 和 LAG 哪个更适合找突变点
看你想标记“突变发生的位置”还是“突变被发现的位置”。LEAD() 把突变归因于当前行(因为当前行和下一行差太大),LAG() 把突变归因于下一行(因为下一行和上一行差太大)——语义不同,输出行数相同,但标记的“责任行”不一样。
例如两行数据:balance = 1000 → balance = 1800,阈值 700:
- 用
LEAD():当前行(1000)的delta = 800,触发报警,突变点记在 1000 这条记录 - 用
LAG():下一行(1800)的delta = 800,触发报警,突变点记在 1800 这条记录 - 多数监控场景选
LEAD(),因为“从这里开始异常”更符合排查直觉
性能和边界情况要注意什么
大表上跑 LEAD() 很容易慢,尤其 PARTITION BY 字段基数高(比如每条记录都是不同 user_id),会导致大量小分组排序。别指望加个索引就能解决——窗口函数优化依赖排序效率,不是简单走索引。
- 先确认
ORDER BY字段有索引,例如(user_id, log_time)复合索引,能避免临时排序 - 如果只是查“有没有突变”,不要 SELECT 所有字段,只取关键列 + delta,减少网络和内存开销
- NULL 值必须处理:balance 为 NULL 时,
LEAD(balance) - balance整个表达式为 NULL;建议提前用COALESCE(balance, 0)或过滤掉 NULL 记录 - MySQL 8.0+、PostgreSQL、SQL Server 2012+、Oracle 10g+ 都支持
LEAD(),但 SQLite 不支持(得用自连接模拟)
真正难的不是写对语法,而是确认“突变”的业务定义是否稳定——比如是否要排除批量导入、定时补账这类已知扰动。这些得靠前置数据打标,不是 LEAD 能解决的。










