LAG()和LEAD()的核心作用是替代自连接,直接获取当前行前/后偏移行的值,必须配合ORDER BY使用,排序字段需具唯一性以防错位,越界默认返回NULL,支持指定偏移量和默认值。
它们直接在当前行上下文中获取偏移后的值,不用写 JOIN、不用建临时表、也不用担心关联条件漏数据。关键前提是必须明确指定 ORDER BY——没有排序就没有“相邻”,这两个函数会直接报错或返回 NULL。
常见错误现象:LAG(value) OVER () 不带 ORDER BY 在 PostgreSQL 报错,在 MySQL 8.0+ 虽不报错但结果不可靠;SQL Server 要求 ORDER BY 必须存在。
OVER (ORDER BY ...) 子句,且排序字段最好有唯一性(如加 id 防止同时间戳导致顺序不确定)LAG() 取前一行,LEAD() 取后一行;默认偏移量是 1,可显式写成 LAG(col, 1) 或 LEAD(col, 2)
NULL,可用第三个参数指定默认值,比如 LAG(amount, 1, 0)
比如销售表 sales 有 date 和 revenue,要算“比前一天增长多少”,传统做法得 JOIN sales s1 ON s2.date = s1.date + INTERVAL '1 day',容易因缺日期断链。用 LAG() 更稳:
SELECT date, revenue, revenue - LAG(revenue, 1) OVER (ORDER BY date) AS diff_from_prev_dayFROM sales;
注意点:
date 有重复,ORDER BY date 会导致相同日期内行序不确定,建议补上 id 或 time: ORDER BY date, id
LAG() 自动跳到上一个实际存在的行,不是“逻辑前一天”,这点和自连接行为不同——后者能强制对齐日历,前者对齐实际记录例如用户操作日志表 user_events 记录 user_id、event_type('login'/'logout')、ts,想查每次 login 后的 logout 时间。用 LEAD() 很自然:
SELECT user_id, ts AS login_ts, LEAD(ts) OVER (PARTITION BY user_id ORDER BY ts) AS next_tsFROM user_eventsWHERE event_type = 'login';
但这里容易踩坑:
PARTITION BY user_id 必须加,否则不同用户的事件混在一起排序,LEAD() 可能取到别人的数据LEAD() 返回 NULL,不能直接当“超时”处理,得结合业务判断是否允许无配对LEAD() 取的是紧邻下一行,未必是“对应 logout”,此时需额外过滤或用条件偏移(如 LEAD(CASE WHEN event_type='logout' THEN ts END))数据库执行 LAG() 时一般只做一次全表扫描 + 排序,然后线性遍历输出;而自连接往往触发嵌套循环或哈希连接,尤其当关联字段无索引时,复杂度接近 O(n²)。
但要注意:
OVER 子句里 ORDER BY 字段没索引,排序成本可能很高,这时自连接配上 date 索引反而更快LAG(col, 100) 可能比 LAG(col, 1) 慢很多;新版本已改善LAG() 在并行查询下表现稳定,但自连接若未正确 hint,容易被优化器选错驱动表真正麻烦的从来不是函数本身,而是排序依据是否真实反映业务顺序——时间字段有精度丢失、字符串时间没标准化、或者用本地时区导致跨日混乱,这些都会让“相邻”变成错位。