FIRST_VALUE返回NULL是因为它不跳过NULL,仅按ORDER BY取首行值;必须用CASE WHEN将NULL排末尾(如ORDER BY CASE WHEN col IS NULL THEN 1 ELSE 0 END, id)才能获取首个非NULL值。

为什么FIRST_VALUE返回NULL而不是你期待的第一个非NULL值
FIRST_VALUE不会自动跳过NULL,它只按窗口定义的排序取第一行的原始值。如果你的排序列本身有NULL、或者ORDER BY没写清楚,FIRST_VALUE(col)很可能直接返回NULL——哪怕后面几行col全是有效值。
常见错误场景:用ORDER BY created_at但某些记录created_at为NULL;或想取“第一个非空邮箱”,却对email列直接开窗。
- 必须显式处理NULL:要么在ORDER BY里把NULL排到最后(如
ORDER BY email IS NULL, email),要么先用CASE过滤出有效值再开窗 - 慎用
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW——它限制了窗口范围,可能让FIRST_VALUE看不到后续的有效值 - 如果目标是“每个分组里第一个非NULL值”,记得加
PARTITION BY group_id,否则整个结果集只算一次
怎么写出真正取“首个非NULL值”的FIRST_VALUE表达式
核心思路是:把你想提取的字段和它的有效性绑定到同一个排序逻辑里。最稳妥的做法是构造一个复合排序键,让非NULL值天然优先。
例如想取每个user_id下最早出现的非空phone:
SELECT
user_id,
FIRST_VALUE(phone) OVER (
PARTITION BY user_id
ORDER BY CASE WHEN phone IS NOT NULL THEN 0 ELSE 1 END, created_at
) AS first_nonnull_phone
FROM users;这里CASE WHEN phone IS NOT NULL THEN 0 ELSE 1 END确保所有非NULLphone排在前面,created_at再决定先后顺序。
- 不要写
ORDER BY phone——字符串排序会让'123'排在'99'前面,且NULL会排最前 - 如果字段类型是数值,用
COALESCE(phone, -999999)排序也行,但语义不如CASE清晰 - MySQL 8.0+ 和 PostgreSQL 支持这个写法;SQLite需确认版本是否支持窗口函数
FIRST_VALUE和COALESCE、LAG混用时的陷阱
FIRST_VALUE是窗口函数,依赖完整的窗口定义;COALESCE是标量函数,只看当前行;LAG查的是上一行——三者行为完全不同,不能互相替代。
典型误用:以为COALESCE(FIRST_VALUE(a), FIRST_VALUE(b))能取“a或b中第一个非NULL”,其实它只是取a的第一个值、再取b的第一个值,然后选非NULL的那个,和“按时间顺序找a或b首次出现的非NULL”不是一回事。
- 要实现“按时间取a或b任一字段首个非NULL”,得先UNPIVOT或用CASE构造统一源列,再开窗
-
LAG无法回溯到分组开头,所以不能替代FIRST_VALUE;反过来,FIRST_VALUE也不能替代LAG查上一行 - 性能注意:多个
FIRST_VALUE叠加(如分别取first_name、last_name)会触发多次窗口计算,不如一次构造结构化中间列
PostgreSQL和SQL Server对NULL处理的细微差别
PostgreSQL默认把NULL排在ORDER BY ... ASC的最前面,SQL Server默认排最后——这直接影响FIRST_VALUE的结果。不显式控制NULL位置,跨数据库迁移时容易出错。
安全写法统一用ORDER BY col IS NULL, col(NULL排后)或ORDER BY col IS NOT NULL DESC, col(非NULL优先):
-- PostgreSQL / SQL Server / MySQL 8.0+ 都兼容 FIRST_VALUE(value) OVER ( PARTITION BY category ORDER BY value IS NULL, updated_at )
- Oracle用
ORDER BY value NULLS LAST,语法不同,不可直接复用 - 某些旧版SQL Server(2012之前)不支持窗口函数,
FIRST_VALUE直接报错 - 如果业务要求严格按插入顺序而非时间戳,记得用
ORDER BY ctid(PostgreSQL)或ORDER BY %%physloc%%(SQL Server),但这些是物理位置,不保证逻辑一致性
实际用的时候,最常被忽略的是ORDER BY子句里没约束NULL位置,导致看似写了FIRST_VALUE,结果一半数据返NULL——不是函数不好使,是排序逻辑没兜住。

















