LAG必须配合ORDER BY,否则“前一行”是随机的;排序字段需唯一确定业务顺序,重复时须加二级排序;缺PARTITION BY会导致跨组混算;首行LAG返回NULL,需显式处理;default参数应依业务语义选择。

LAG必须配合ORDER BY,否则“前一行”是随机的
不写ORDER BY的LAG()没有业务意义——数据库不保证物理存储顺序,所谓“上一行”可能是任意一行。你看到的“似乎对了”,只是巧合,换数据或换版本就崩。
排序字段必须能唯一确定业务先后:想比“时间上一条”,就ORDER BY event_time;想按版本递增比,就ORDER BY version_id。如果event_time有重复(比如毫秒级精度丢失),必须加二级排序,例如ORDER BY event_time, id,否则结果非确定。
- MySQL/PostgreSQL/SQL Server 都要求
ORDER BY显式存在,漏写会报错或返回不可靠值 - SQLite 支持
LAG但排序无强制校验,更容易踩坑 - Hive 3.1+ 支持,但旧版(如 Hive 2.x)可能不识别
ORDER BY在窗口中的位置,需检查执行计划
标记“变化项”时,首行NULL不能直接用!=判断
LAG()对每个分组的第一行返回NULL,这是设计行为,不是bug。如果写status != LAG(status),首行就会被误判为“变化”,因为NULL != anything永远为UNKNOWN,在CASE或WHERE中实际等效于FALSE。
安全做法是显式处理NULL:
- PostgreSQL 可用
status IS DISTINCT FROM LAG(status),它把NULL当普通值比较 - MySQL / SQL Server 推荐用
CASE WHEN LAG(status) IS NULL THEN 'first' WHEN status != LAG(status) THEN 'changed' ELSE 'same' END - 别在
WHERE里写LAG(status) IS NOT NULL——窗口函数不能出现在WHERE,得包一层子查询或CTE
PARTITION BY漏写会导致跨组混比,结果完全错乱
如果你要查“每个用户登录状态是否变化”,却没写PARTITION BY user_id,那么LAG(status)会在全表排序后取上一行——A用户最后一条记录的前一行,很可能是B用户的某条记录。这种错误不会报错,但结果毫无业务价值。
典型错误写法:LAG(status) OVER (ORDER BY login_time)(缺PARTITION BY)
正确写法:LAG(status) OVER (PARTITION BY user_id ORDER BY login_time)
- 多维分组时,
PARTITION BY可接多个字段,如PARTITION BY region, product_type - 分组键和排序键的组合必须覆盖业务粒度,避免把本该独立计算的记录挤进同一窗口
- 大数据量下,漏
PARTITION BY还可能触发全表排序,导致OOM或超时
差值计算中default参数不是装饰,选错会污染整列
LAG(col, 1, 0)和LAG(col, 1)在首行表现一致(都返回0 vs NULL),但后续逻辑影响巨大。比如做减法:col - LAG(col, 1)遇到NULL整行变NULL;而col - LAG(col, 1, 0)至少能算出col本身。
default值选什么,取决于业务语义:
- 累计类指标(如日活增量):填
0合理,表示“前一天没有数据” - 状态继承类(如配置生效值):填
col自身更稳妥,即LAG(col, 1, col),避免首行断档 - 百分比计算(如增长率):default填
NULL反而更安全,避免用0当分母引发除零或误导性正增长
注意:SQLite 的default固定为NULL且不可改,必须用COALESCE(LAG(col), 0)兜底;Hive 旧版本也不支持第三参数,同样得套COALESCE。

















