MAX(value) OVER (ORDER BY ts ROWS UNBOUNDED PRECEDING) 能向前填充,是因为它在累积窗口中忽略NULL、延续上一个非空最大值;但仅适用于单调不减字段,不适用于波动型数据,真正可靠填充需先按非空值分组再用MAX()。

MAX() OVER 可以向前填充最近非空值,但仅限于“当前行及之前所有行中最大的那个非空值”——它不感知时间距离,也不跳过空值找锚点,本质是累积最大值,不是填充逻辑。
为什么 MAX(value) OVER (ORDER BY ts) 能“看起来像填充”?
因为 MAX() 在窗口内遇到 NULL 时不中断,会延续上一个非空值的结果(NULL 不参与比较,不影响最大值)。只要数据按时间有序、且非空值单调递增或不变,它就“恰好”表现出前向填充效果。
- 适用场景:金额、等级、状态码等天然有单调性或离散枚举的字段(如用户等级从1→2→3不再回落)
- 不适用场景:温度、股价、评分等可能上下波动的数值——
MAX()会把后面更低的值也“撑高”,造成失真 - 关键限制:窗口必须用
ROWS UNBOUNDED PRECEDING,否则默认RANGE模式在时间戳重复时会出错
如何正确构造分组 + MAX 实现真正向前填充?
真正的“向前填充到最近非空值”需要先划分连续空值段,再对每段取源头值。MAX() 在这里只是工具,核心是分组逻辑:
- 用
SUM(value IS NOT NULL) OVER (ORDER BY ts)生成grp:每遇到一个非空value,组号加 1;空值继承前一组号 - 再用
MAX(value) OVER (PARTITION BY grp):每组内MAX自然返回那个唯一的非空源头值 - 注意:若开头全是
NULL,grp = 0段无非空值,MAX返回NULL,符合预期
示例语句(MySQL / PostgreSQL 兼容):
SELECT ts, value,
MAX(value) OVER (PARTITION BY grp) AS filled_value
FROM (
SELECT ts, value,
SUM(value IS NOT NULL) OVER (ORDER BY ts) AS grp
FROM your_table
) t;
MAX() 填充 vs FIRST_VALUE() 填充的区别在哪?
两者在“单段内首个非空值向后广播”目标下结果一致,但行为机制不同:
-
FIRST_VALUE(value) OVER (PARTITION BY grp ORDER BY ts)严格取排序后第一行的value,哪怕该行是NULL(需加IGNORE NULLS才安全,但 MySQL 8.0+ 不支持) -
MAX(value) OVER (PARTITION BY grp)天然忽略NULL,只要组内有一个非空值,就返回它;全NULL组才返回NULL - 性能上,
MAX通常比FIRST_VALUE更轻量,尤其在大分组时
容易被忽略的边界问题
三个实际踩坑点必须手动处理:
- 时间戳重复:若
ts不唯一,ORDER BY ts无法保证稳定排序,需补上唯一列(如自增id):ORDER BY ts, id - 开头连续
NULL:MAX填充无法“向后借”,此时得结合LEAD()或子查询兜底,不能只靠一个窗口函数 - 跨分组污染:没写
PARTITION BY就直接MAX() OVER (ORDER BY ts),会导致早期非空值影响后续所有组——这是最常见误用
真正可靠的填充,从来不是选一个函数,而是先定义“什么是最近有效值”,再匹配函数能力。MAX() 只适合其中一类简单情况。

















