FIRST_VALUE()默认窗口帧为RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,仅覆盖当前行及之前行,非整组;需显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING并确保ORDER BY稳定唯一,才能获取整组首值。

默认窗口帧只覆盖“当前行及之前”,不是整组
FIRST_VALUE() 默认的窗口帧是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,它不看整个分组,只从分区开头扫到当前行。所以第 5 行的 FIRST_VALUE(name) 返回的是“这行及前面 4 行里排序第一的 name”,而不是“整个分组的第一条”。结果随行位置漂移,尤其当 ORDER BY 字段有重复值时,数据库可能任意选一个——你看到的“不对”,其实是定义使然。
必须显式写全帧定义:FIRST_VALUE(name) OVER (PARTITION BY user_id ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)。注意用 ROWS,不是 RANGE;UNBOUNDED FOLLOWING 才能覆盖整组。
ORDER BY 缺失或不稳定,导致“第一行”不可控
没写 ORDER BY,或者排序字段存在大量重复(比如多个记录同一天创建),FIRST_VALUE() 就没法确定哪条是“第一”。数据库会按存储顺序或内部 hash 随机取,结果每次查询都可能不同。
- 必须明确写
ORDER BY created_at, id这类组合,用唯一字段兜底 -
created_at为 NULL 的行会被排在最前或最后(依数据库而定),建议提前WHERE created_at IS NOT NULL或用COALESCE(created_at, '1970-01-01') - MySQL 8.0+、PostgreSQL、SQL Server 支持该写法;SQLite 3.25+ 也认,但旧版不支持
ROWS子句
把 FIRST_VALUE 当成筛选工具,但它根本不删行
常见误解:写 SELECT *, FIRST_VALUE(id) OVER (PARTITION BY category ORDER BY ts) 想“取出每组第一条记录”,结果发现行数没变——这完全正常。FIRST_VALUE 是窗口函数,只补一列值,不是过滤器。你真正要的是“每组留一行”,得靠 ROW_NUMBER() 配合 WHERE rn = 1,或用 QUALIFY(BigQuery/Snowflake/PostgreSQL 14+)。
如果硬要用 FIRST_VALUE 拿多字段,得每个字段单独套一遍相同窗口定义:FIRST_VALUE(name) OVER w、FIRST_VALUE(status) OVER w……不仅啰嗦,还容易漏字段或窗口不一致。
NULL 值处理不当,返回意外空值
FIRST_VALUE(status) 默认 RESPECT NULLS,遇到分组首行就是 NULL,就直接返回 NULL;如果想跳过 NULL 取第一个非空值,得加 IGNORE NULLS(PostgreSQL 13+、Oracle、Databricks、Redshift 支持,MySQL 和 SQL Server 不支持)。
兼容写法是用 CASE + 排序权重:ORDER BY CASE WHEN status IS NULL THEN 1 ELSE 0 END, created_at;或者用 COALESCE(FIRST_VALUE(status) ..., 'unknown') 做 fallback,但要注意:如果整组 status 全是 NULL,FIRST_VALUE 仍返回 NULL,COALESCE 才兜底。
ROWS 或漏掉唯一排序字段,结果就可能出人意料。

















