LAST_VALUE 默认不适合向前填充——它取当前窗口末尾值,须显式指定 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 才能逐行获取最近非空值;MySQL 默认 RANGE 帧更易出错,且不自动跳过 NULL,需 IGNORE NULLS 或预处理。

LAST_VALUE 真的适合向前填充吗?
直接说结论:LAST_VALUE 默认行为**不适合**向前填充缺失值——它默认是“窗口末尾”取值,且 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 才能保证“到当前行为止最近的非空值”,否则容易取到后面(甚至 NULL)的值。很多人一看到“LAST”就直觉以为是“往前找最后一个”,其实它是“在当前窗口里取最后一个”,而窗口方向由 ORDER BY 和 ROWS 共同决定。
必须显式指定窗口帧才能安全使用 LAST_VALUE
不写 ROWS 子句时,PostgreSQL/SQL Server/Oracle 的默认窗口帧是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,但 **MySQL 8.0+ 默认是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW**,在存在重复排序键时可能跨行取错值。所以必须显式声明:
-
LAST_VALUE(col) OVER (ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)—— 安全,逐行累积取最近非空 - 不能只写
LAST_VALUE(col) OVER (ORDER BY ts)—— MySQL 下可能跳过中间 NULL,取到更早或更晚的错误值 - 如果列本身含 NULL,
LAST_VALUE会把 NULL 当作有效值传递,需配合IGNORE NULLS(仅 Oracle/PostgreSQL 14+ 支持),否则得先用CASE过滤
替代方案:COALESCE + LAG 递归更可控
对简单单层前向填充(比如按时间顺序补上一个非空值),LAG 链式调用比 LAST_VALUE 更直观、兼容性更好:
SELECT ts,
COALESCE(val,
LAG(val) OVER (ORDER BY ts),
LAG(val, 2) OVER (ORDER BY ts),
LAG(val, 3) OVER (ORDER BY ts)) AS filled_val
FROM t;但注意:这只能回溯固定 N 步,不如窗口函数灵活;若缺失连续多行,仍需嵌套或改用递归 CTE。真正健壮的前向填充,推荐用 FIRST_VALUE 配合逆序 ORDER BY:
-
FIRST_VALUE(val) IGNORE NULLS OVER (ORDER BY ts DESC ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)—— 本质是“从当前行往未来找第一个非空”,等价于向前填充语义 - 这个写法在 PostgreSQL 14+、Oracle、BigQuery 中可用;SQL Server 2022+ 支持
IGNORE NULLS,旧版需用子查询模拟
实际填充时最容易被忽略的陷阱
前向填充不是纯语法问题,更是业务逻辑问题:
- 排序字段
ts必须严格单调(无重复、无乱序),否则窗口边界失效;有重复时务必加唯一辅助列如id:ORDER BY ts, id -
LAST_VALUE返回的是窗口内最后一行的原始值,**不会自动跳过 NULL** —— 如果你希望“跳过 NULL 取前一个非空”,就必须用IGNORE NULLS或提前用COALESCE(val, ...)构造中间列 - 分区填充(如按 user_id 分组填充)时,
PARTITION BY user_id ORDER BY ts缺一不可,漏掉PARTITION BY会导致跨用户污染
真正难的从来不是写出那行 LAST_VALUE,而是确认你的“向前”在业务上是否允许跨天、跨状态、跨设备——这些没法靠 SQL 函数兜底。

















