LEAD(status)需配合PARTITION BY order_id和ORDER BY created_at才能正确获取“变更后状态”,否则顺序错乱;返回NULL常见于末行、时间字段为NULL、精度不足或存在脏数据。

LEAD函数怎么写才能拿到“变更后状态”
直接用 LEAD(status) 就行,但必须配合 ORDER BY created_at(或时间戳字段),否则顺序错乱,返回的“下一个状态”毫无意义。常见错误是只按订单号分组却忘了排序,导致 LEAD() 拿到的是随机下一行——不是时间上紧邻的变更。
示例(PostgreSQL/MySQL 8.0+/SQL Server):
SELECT
order_id,
status AS current_status,
LEAD(status) OVER (
PARTITION BY order_id
ORDER BY created_at
) AS next_status
FROM orders;注意:PARTITION BY order_id 是必须的,否则跨订单混排;created_at 必须是精确到秒(或毫秒)的时间字段,不能用日期类型或字符串时间。
为什么LEAD返回NULL?常见三类原因
LEAD() 在每组最后一行默认返回 NULL,这是正常行为,但实际中容易误判为数据缺失。真正要排查的是以下情况:
- 时间字段含
NULL值:排序时NULL可能被排在最前或最后(依数据库而定),导致LEAD()跳过有效行 -
created_at精度不足:多条记录时间相同,数据库内部排序不稳定,LEAD()结果不可预测 - 未过滤测试/脏数据:比如状态为
'INIT'的初始化记录没有对应变更,但被纳入计算,拉长了分组长度
建议先执行 SELECT order_id, COUNT(*) FROM orders GROUP BY order_id HAVING COUNT(*) = 1,确认单状态订单数量,心里有底再查轨迹。
只想要“发生过变更”的订单行,怎么过滤
不能直接在主查询里加 WHERE next_status IS NOT NULL——窗口函数不能在 WHERE 中引用。必须用子查询或 CTE:
WITH status_with_next AS (
SELECT
order_id,
created_at,
status,
LEAD(status) OVER (
PARTITION BY order_id
ORDER BY created_at
) AS next_status
FROM orders
)
SELECT * FROM status_with_next
WHERE next_status IS NOT NULL AND next_status != status;第二层过滤 next_status != status 很关键:有些系统会重复插入相同状态(如定时同步),这类“伪变更”必须排除。另外,created_at 相同但状态不同的记录,属于并发写入异常,LEAD() 无法保证顺序,得靠应用层或唯一约束提前拦截。
性能差、查不动?检查这三个地方
百万级订单表跑 LEAD() 明显变慢,通常卡在:
- 缺少复合索引:
(order_id, created_at)是黄金组合,缺一不可。单列索引无效 - status 字段过大:如果用
TEXT或长VARCHAR存状态名,排序开销陡增。建议用短代码(如'P','S','C')+ 字典表关联 - 未限制时间范围:全表扫描不可避免。务必加上
WHERE created_at >= '2024-01-01'类条件,让索引生效
如果只是临时分析,又不想改表结构,可以先用 CREATE TEMP TABLE 抽出最近30天数据再算——比硬扛全量快得多。

















