必须用 lag() 因为它是一行内窗口函数,无需关联查询,避免子查询的重复扫描和性能开销,语义明确且执行效率高。

直接用 LAG() 就行,不用子查询、不用自连接,性能好且语义清晰。
为什么必须用 LAG() 而不是子查询
子查询写法(比如 (SELECT value FROM t WHERE time )在大数据量下会严重拖慢查询速度,每次都要扫描前序数据;而且无法在 <code>WHERE 中过滤差值结果(会报 Unknown column 'diff' in 'where clause')。LAG() 是窗口函数,在排序后一次性完成偏移取值,执行计划更可控。
- MySQL 8.0+、PostgreSQL、SQL Server 2012+、Oracle 都原生支持
LAG() - SQLite 3.25+ 也支持,但不支持第三个默认值参数
- 旧版 MySQL(
LAG() 的三个参数怎么填才不出错
标准写法是 LAG(<code>column, n, default),最容易错的是第二、三参数的组合逻辑:
- 第一个参数必须是列名,不能是表达式(如
LAG(a - b)会报错,得先算好再传入) - 第二个参数
n是“往上第几行”,默认为1,写成2就跳过上一行取上上行 - 第三个参数是当取不到时的兜底值,比如首行没有“上一行”,
LAG(value, 1, 0)就返回0,不写则返回NULL - 务必配
ORDER BY子句,否则窗口无序,LAG()结果不可预测
示例(按时间倒序计算当前值与上一笔的差):
SELECT DTime, SValue, LAG(SValue, 1, 0) OVER (ORDER BY DTime) AS prev_value, SValue - LAG(SValue, 1, 0) OVER (ORDER BY DTime) AS diff FROM TCATDataTime;
分组内跨行减法必须加 PARTITION BY
如果数据含多个设备、用户或指标(比如 device_id),只靠 ORDER BY 会把不同组混在一起算。这时候必须用 PARTITION BY device_id 切分窗口:
- 漏掉
PARTITION BY是最隐蔽的 bug:差值看起来有数,但其实是跨设备相减 -
PARTITION BY和ORDER BY必须同时存在,顺序无关,但缺一不可 - 分区字段类型要一致,比如
tenant_id是字符串就别和整数混用
示例(每个 SData 分组内独立计算差值):
SELECT SData, DTime, SValue, SValue - LAG(SValue) OVER (PARTITION BY SData ORDER BY DTime) AS diff_per_group FROM TCATDataTime;
性能和 NULL 处理的几个硬细节
LAG() 本身很快,但实际慢往往卡在别处:
- 没给排序字段建索引(如
DTime)会导致OVER (ORDER BY DTime)全表排序,I/O 暴涨 - 用
LAG(..., 1, 0)后,差值为0不代表没变化——可能是当前值真等于上一行,也可能是上一行根本不存在而用了默认值 - 想排除首行(差值为默认值的行),不能写
WHERE diff = 0,得用WHERE prev_value IS NOT NULL或显式判断 - 某些场景需要
LEAD()(往下取),逻辑对称,但别和LAG()混用在同一OVER子句里
真正容易被忽略的点:窗口函数的执行时机晚于 WHERE,早于 ORDER BY 和 LIMIT —— 所以差值列不能直接在 WHERE 里引用,得套一层子查询或用 CTE。










