差分变化量是当前行与上一行(或下一行)某字段的差值,需用lag()/lead()配合partition by和order by实现;必须排序以定义“前后”,重复值需二级排序;首行lag返回null,应显式处理;默认值参数和索引对结果准确性与性能至关重要。

什么是窗口函数里的差分变化量
差分变化量,就是当前行和上一行(或下一行)某个字段的差值。在 SQL 里,它不是靠 GROUP BY + 聚合实现的,而是用窗口函数 LAG() 或 LEAD() 拿到相邻行值后手动相减。关键点在于:必须先按业务逻辑排序(比如时间、序号),否则“上一行”没意义。
用 LAG() 计算组内逐行差分的写法
最常见需求是:每个用户每天的访问量,求“比前一天增长多少”。这时要先按 user_id 分组,再按 date 排序,然后取前一行的值做减法。
实操建议:
-
LAG()第二个参数填1表示跳 1 行(默认就是 1,可省略);第三个参数是当没有上一行时的默认值,建议显式写成0或NULL,避免隐式类型转换出错 -
OVER子句里必须同时有PARTITION BY(分组)和ORDER BY(排序),漏掉ORDER BY会导致结果不可预测 - 如果原始数据里存在重复时间戳,
ORDER BY要加二级排序(如ORDER BY date, id),否则窗口顺序不唯一
示例:
SELECT user_id, date, visits, visits - LAG(visits, 1, 0) OVER (PARTITION BY user_id ORDER BY date) AS diff FROM user_daily_stats;
遇到 NULL 差分结果怎么办
首行计算 LAG() 必然返回 NULL(因为没“上一行”),导致整个 diff 列首行为 NULL。这不是 bug,是预期行为。但业务常要求首行显示为 0 或原值。
解决方法:
- 用
COALESCE()包一层:COALESCE(visits - LAG(visits) OVER (...), 0) - 或者把差分逻辑改写成:
CASE WHEN LAG(visits) OVER (...) IS NULL THEN 0 ELSE visits - LAG(visits) OVER (...) END - 注意:别直接在
LAG()的默认值里填visits——那是当前行值,不是上一行,会破坏差分语义
性能和兼容性要注意什么
窗口函数本身不触发临时表或文件排序,但 ORDER BY 部分依赖索引。如果 PARTITION BY a ORDER BY b 没有联合索引 (a, b),大表上可能明显变慢。
兼容性方面:
- MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持标准语法
- SQLite 3.25+ 支持,但不支持
ROWS BETWEEN等高级帧子句 - 旧版 MySQL(5.7 及以前)完全不支持,只能用自连接或变量模拟,极易出错且不可靠
真正容易被忽略的是:差分结果不能直接用于后续的窗口聚合(比如再套一层 SUM() OVER),因为窗口函数不能嵌套。得用 CTE 或子查询先落地差分列。











