在SQL数据处理工作中,对比当前行与前一行的数据差异是高频需求,比如计算每日销售额的环比变化、统计相邻两次登录的时间间隔、判断连续数值的波动情况等。传统的实现方式往往需要借助自连接或者子查询,逻辑复杂且执行效率较低,而LAG函数的出现很好地解决了这个问题。

LAG函数基础语法
LAG函数属于SQL窗口函数的一种,作用是返回当前行之前指定偏移量的行的列值,基础语法如下:
-- LAG函数基础语法
LAG(column_name, offset, default_value) OVER (
[PARTITION BY partition_column]
ORDER BY sort_column [ASC|DESC]
)
各个参数的含义如下:
- column_name:要获取前一行值的列名,必填参数
- offset:偏移量,表示往前取第几行,默认值为1,也就是前一行,可选参数
- default_value:当偏移量对应的行不存在时返回的默认值,比如第一行没有前一行,就会返回这个值,可选参数,默认是NULL
- PARTITION BY:可选的分区子句,用于将数据集按照指定列分组,在每个分组内单独计算LAG值
- ORDER BY:必填的排序子句,指定每个分区内行的排序规则,LAG函数依赖排序结果确定前一行是哪一行
基础用法:对比当前行与前一行差异
假设我们有一张每日销量表daily_sales,表结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| sale_date | DATE | 销售日期 |
| product_id | INT | 商品ID |
| sale_amount | DECIMAL | 当日销量 |
现在需要计算每个商品每日销量和前一天销量的差值,使用LAG函数的实现代码如下:
-- 计算每个商品每日销量与前一天的差异
SELECT
product_id,
sale_date,
sale_amount,
-- 获取前一天的销量,没有前一天则返回0
LAG(sale_amount, 1, 0) OVER (
PARTITION BY product_id
ORDER BY sale_date
) AS prev_day_sale,
-- 计算当前销量和前一天销量的差值
sale_amount - LAG(sale_amount, 1, 0) OVER (
PARTITION BY product_id
ORDER BY sale_date
) AS sale_diff
FROM daily_sales
ORDER BY product_id, sale_date;
上述代码中,我们先按照product_id分区,保证每个商品的销量数据单独计算,再按照sale_date升序排序,LAG函数就会在每个商品的分区内,按照日期顺序获取前一行的sale_amount值,默认偏移量为1,没有前一行时返回0,最后用当前销量减去前一天销量得到差异值。
进阶用法:指定偏移量和默认值
LAG函数的偏移量不只能是1,也可以设置为更大的值,比如需要对比当前行和前三行的差异,就可以把offset设置为3。同时可以自定义默认值,避免返回NULL影响后续计算。
比如需要计算每个商品当前销量和前3天销量的差值,前3天没有数据则返回-1,实现代码如下:
-- 计算当前销量和前3天销量的差异
SELECT
product_id,
sale_date,
sale_amount,
LAG(sale_amount, 3, -1) OVER (
PARTITION BY product_id
ORDER BY sale_date
) AS prev_3day_sale,
sale_amount - LAG(sale_amount, 3, -1) OVER (
PARTITION BY product_id
ORDER BY sale_date
) AS sale_diff_3day
FROM daily_sales
ORDER BY product_id, sale_date;
多场景应用示例
场景1:计算相邻订单的时间间隔
假设有订单表order_info,包含order_id、user_id、order_time字段,需要计算每个用户相邻两次下单的时间间隔,代码如下:
-- 计算用户相邻订单的时间间隔(单位:分钟)
SELECT
user_id,
order_id,
order_time,
LAG(order_time) OVER (
PARTITION BY user_id
ORDER BY order_time
) AS prev_order_time,
-- 计算时间差,转换为分钟
TIMESTAMPDIFF(MINUTE,
LAG(order_time) OVER (
PARTITION BY user_id
ORDER BY order_time
),
order_time
) AS order_interval_min
FROM order_info
ORDER BY user_id, order_time;
场景2:判断连续数值的波动情况
假设有温度记录表temperature_record,包含record_time、temp_value字段,需要判断每次温度相比前一次是上升、下降还是持平,代码如下:
-- 判断温度波动情况
SELECT
record_time,
temp_value,
LAG(temp_value) OVER (ORDER BY record_time) AS prev_temp,
CASE
WHEN temp_value > LAG(temp_value) OVER (ORDER BY record_time) THEN '上升'
WHEN temp_value < LAG(temp_value) OVER (ORDER BY record_time) THEN '下降'
ELSE '持平'
END AS temp_change
FROM temperature_record
ORDER BY record_time;
使用注意事项
- LAG函数必须配合
OVER子句中的ORDER BY使用,否则无法确定前一行的位置,查询结果会不符合预期 - 如果使用了
PARTITION BY分区,LAG函数只会在当前分区内查找前一行,不会跨分区取值 - 默认情况下,偏移量对应的行不存在时返回NULL,如果需要后续做数值计算,建议设置合适的默认值,避免NULL参与计算导致结果异常
- 不同数据库对LAG函数的支持略有差异,比如MySQL从8.0版本开始支持窗口函数,低版本MySQL无法使用LAG函数,需要改用自连接实现
LAG函数和LEAD函数是一对互补的窗口函数,LEAD函数用于获取当前行之后指定偏移量的行值,两者结合可以实现前后行的双向对比,适用更多复杂的数据分析场景。