lag()/lead()仅取相邻非空值,无法跳过连续空值找最近有效点;线性插值需前后最近非空值及其位置,通过last_value/ first_value ignore nulls与时间戳提取实现加权计算。

为什么不能直接用 LAG() 和 LEAD() 做线性插值?
因为这两个函数只能取到前后第一个非空值,而线性插值需要知道「前一个非空值的位置和值」以及「后一个非空值的位置和值」,再按距离加权。直接 LAG(col) 遇到连续缺失就失效——它返回 NULL,无法跳过中间的空值找最近的有效点。
用窗口函数定位前后最近非空值的行号和值
核心是两次窗口聚合:一次正向累积(找前一个非空),一次反向累积(找后一个非空)。关键不在于值本身,而在于用 ROW_NUMBER() 或 MAX() OVER (... ROWS BETWEEN ...) 锚定位置。
- 用
MAX(val) FILTER (WHERE val IS NOT NULL) OVER (ORDER BY ts ROWS UNBOUNDED PRECEDING)可得「截至当前行最近的前向非空值」(PostgreSQL 14+) - 在不支持
FILTER的 MySQL 8.0+ 或 SQL Server 中,改用LAST_VALUE(val) IGNORE NULLS OVER (ORDER BY ts ROWS UNBOUNDED PRECEDING) - 同理,后向用
FIRST_VALUE(val) IGNORE NULLS OVER (ORDER BY ts ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) - 还需同步获取对应的时间戳(或序号),用于计算权重;建议用
MAX(ts) FILTER (WHERE val IS NOT NULL) OVER (...)同步提取时间
算出插值系数并完成线性计算
拿到前值 prev_val、后值 next_val、前时间 prev_ts、后时间 next_ts 和当前时间 curr_ts 后,公式就是:(next_val - prev_val) * (curr_ts - prev_ts) / (next_ts - prev_ts) + prev_val。注意除零保护——当 prev_ts = next_ts(即所有时间戳相同),直接取 prev_val 即可。
- MySQL 中需用
COALESCE(..., prev_val)防止除零报错 - PostgreSQL 可用
NULLIF(next_ts - prev_ts, 0)配合NULLIF安全除法 - 如果时间列是日期类型,先转为
EPOCH秒数或用EXTRACT(EPOCH FROM ...)统一单位
完整可运行示例(PostgreSQL)
SELECT
ts,
val,
COALESCE(
val,
(
(next_val - prev_val) * (EXTRACT(EPOCH FROM ts) - prev_epoch)
/ NULLIF(next_epoch - prev_epoch, 0)
+ prev_val
)::NUMERIC(10,3)
) AS val_interp
FROM (
SELECT
ts,
val,
LAST_VALUE(val) IGNORE NULLS OVER (ORDER BY ts ROWS UNBOUNDED PRECEDING) AS prev_val,
FIRST_VALUE(val) IGNORE NULLS OVER (ORDER BY ts ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS next_val,
MAX(CASE WHEN val IS NOT NULL THEN EXTRACT(EPOCH FROM ts) END)
OVER (ORDER BY ts ROWS UNBOUNDED PRECEDING) AS prev_epoch,
MIN(CASE WHEN val IS NOT NULL THEN EXTRACT(EPOCH FROM ts) END)
OVER (ORDER BY ts ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS next_epoch
FROM samples
) t;
连续缺失段两端必须有非空值,否则 prev_val 或 next_val 为 NULL,插值结果仍为 NULL——这是线性插值的天然限制,不是写法问题。










