ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING 才能精确取上一行值,因其定义仅含单行的窗口;误写为 1 PRECEDING AND CURRENT ROW 会包含两行导致聚合结果错误,LAG() 是更安全直观的替代方案。

为什么 ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING 才能取上一行
很多人误写成 ROWS BETWEEN 1 PRECEDING AND CURRENT ROW,结果得到的是「当前行 + 上一行」的窗口聚合(比如求和),而非单个上一行值。真正要取上一行做差值,必须把窗口缩到仅含上一行——也就是起点和终点都是 1 PRECEDING。
这个语法本质是定义一个“只有一行”的滑动窗口,配合 LAG() 更直观,但如果你坚持用窗口帧(比如需要动态偏移或兼容不支持 LAG() 的旧版本),就必须这样写。
LAG() 是更安全、更可读的替代方案
绝大多数场景下,直接用 LAG() 函数比手写 ROWS BETWEEN 更可靠:
-
LAG(value, 1) OVER (ORDER BY ts)明确表达“取上一行的value” - 自动处理首行(返回
NULL,可加默认值:LAG(value, 1, 0) OVER (...)) - 不依赖
ROWS帧的边界语义,避免因ORDER BY重复值导致的非确定性排序问题 - 在 PostgreSQL、SQL Server、Oracle、BigQuery、DuckDB 中都支持;MySQL 8.0+ 也支持
用 ROWS BETWEEN 做差值的实操要点
如果硬要用窗口帧实现差值,关键不是写对帧范围,而是选对聚合函数:
- 必须用
MAX()或MIN()(单值窗口里二者等价),不能用AVG()或SUM()—— 否则语义错乱 - 完整写法:
value - MAX(value) OVER (ORDER BY ts ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) - 注意
ORDER BY列必须有唯一性保证,否则多行同序时窗口行为不可控(例如时间戳没带毫秒,或没加二级排序) - PostgreSQL 允许这种写法;SQLite 3.25+ 支持;MySQL 8.0 支持;但 SQL Server 不支持在窗口帧中用聚合函数引用同一列做计算(会报错)
容易被忽略的 NULL 和排序陷阱
差值计算最常崩在两个地方:
- 首行没有“上一行”,
LAG()或窗口帧都会返回NULL,直接参与减法会导致整行结果为NULL—— 必须用COALESCE(lag_value, 0)或类似方式兜底 -
ORDER BY缺少唯一键时,数据库可能任意排列相同排序值的行,导致“上一行”不稳定。正确做法是补一个唯一列:例如ORDER BY ts, id - 如果原始数据有空值,
LAG()会照常返回NULL,但差值表达式如value - LAG(value)会整体变NULL,需提前WHERE value IS NOT NULL或用IGNORE NULLS(仅 Oracle/BigQuery 支持)
真正在生产环境跑两行差值,别纠结 ROWS BETWEEN 的写法,优先用 LAG(),再把排序和空值兜底做扎实。

















