FIRST_VALUE常返回NULL是因为它不跳过NULL值,仅取排序后物理首行的值;需用CASE WHEN将NULL排末尾并辅以id等确定性字段排序,才能获取首个非空值。

FIRST_VALUE 为什么常返回 NULL 而不是你想要的第一个非 NULL 值?
FIRST_VALUE 本身不跳过 NULL,它只按窗口排序取物理第一行的值。如果排序后首行的列值是 NULL,结果就是 NULL——哪怕后面有非 NULL 值。这不是 bug,是设计如此。
常见错误现象:FIRST_VALUE(status) OVER (PARTITION BY user_id ORDER BY created_at) 返回 NULL,因为某用户最早一条记录的 status 恰好为空。
- 必须显式过滤或重排,让非 NULL 值优先出现在窗口首行
- 不能依赖数据库自动“忽略 NULL”
- ORDER BY 的表达式要能区分 NULL 和非 NULL
用 COALESCE + ORDER BY 实现“首个非 NULL 值”的稳定写法
核心思路:把 NULL 排到窗口末尾,确保第一个非 NULL 值成为 FIRST_VALUE 的目标。
实操建议:
- 在
ORDER BY中用CASE WHEN column IS NULL THEN 1 ELSE 0 END升序,让非 NULL 先出现 - 再按业务字段(如时间、ID)二次排序,保证顺序可预期
- 最后套
FIRST_VALUE(column),此时首行必为非 NULL(若存在)
示例:
SELECT
user_id,
created_at,
status,
FIRST_VALUE(status) OVER (
PARTITION BY user_id
ORDER BY CASE WHEN status IS NULL THEN 1 ELSE 0 END, created_at
) AS first_non_null_status
FROM events;
替代方案:用 FILTER + FIRST_VALUE(PostgreSQL 13+)更直观
PostgreSQL 支持 FILTER 子句,可直接限定聚合/窗口函数作用范围,语义清晰。
注意点:
-
FILTER不是所有数据库都支持(MySQL、SQL Server 不行;Oracle 用KEEP DENSE_RANK替代) - 写法简洁但需确认版本:
FIRST_VALUE(status) FILTER (WHERE status IS NOT NULL) OVER (PARTITION BY user_id ORDER BY created_at) - 如果分组内全为 NULL,结果仍是 NULL —— 这点和前一种写法一致,需额外处理
容易被忽略的边界:空分组与全 NULL 分组
无论用哪种方法,只要一个分组里所有目标列值都是 NULL,FIRST_VALUE 就返回 NULL。这常被当成“逻辑错误”,其实是符合定义的。
如果你需要 fallback 值(比如默认 'unknown'),不能靠窗口函数本身解决:
- 必须用
COALESCE(..., 'unknown')包裹整个FIRST_VALUE表达式 - 或者在外部用
CASE WHEN COUNT(col) = 0 THEN ...预判,但性能开销更大 - 别忘了检查原始数据质量:全 NULL 分组是否本就该被排除?
真正麻烦的是隐式类型转换导致的“看似非 NULL 实则无效”——比如空字符串、全空格、'N/A' 字符串,这些得靠业务规则清洗,FIRST_VALUE 无能为力。











