first_value是窗口函数,不减少行数,仅返回分组内排序后首行指定字段值;获取完整首条记录须用row_number()编号后where rn=1筛选,或qualify(支持引擎)。

FIRST_VALUE 是窗口函数,不是过滤函数
直接用 FIRST_VALUE 不会减少行数,它只是为每行返回组内第一行的某个字段值。想“获取首条记录”,本质是筛选——得配合 ROW_NUMBER() 或用子查询 + QUALIFY(支持的数据库如 Snowflake、BigQuery),而不是依赖 FIRST_VALUE 本身。
正确写法:用 ROW_NUMBER() 配合 WHERE 筛选
这是最通用、兼容性最好的方式,适用于 PostgreSQL、SQL Server、MySQL 8.0+、Oracle 等主流引擎:
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at ASC) AS rn
FROM orders
) t
WHERE rn = 1;
-
PARTITION BY category定义分组依据,替换成你的实际分组字段 -
ORDER BY created_at ASC决定哪条算“首条”,时间戳、ID 或其他业务排序字段都可 - 别用
ORDER BY created_at DESC后再取rn = 1试图拿最新记录——逻辑没错,但容易和“首条”语义混淆 - MySQL 5.7 及更早不支持窗口函数,只能用自关联或变量模拟,性能差且易出错
FIRST_VALUE 的典型误用与真实用途
有人写 SELECT FIRST_VALUE(id) OVER (PARTITION BY category ORDER BY created_at) 想“取出首条 id”,结果发现返回的行数没变——这很正常,FIRST_VALUE 就是干这个的:
SELECT category, name, FIRST_VALUE(name) OVER (PARTITION BY category ORDER BY created_at) AS first_name_in_group FROM products;
- 每行都带一个
first_name_in_group字段,值是该category下最早那条的name - 可用于比对:比如标记当前行是否就是组内首条:
CASE WHEN name = FIRST_VALUE(name) OVER (...) THEN 'first' ELSE 'other' END - 注意默认窗口帧是
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,若需严格按整个分区取值,显式写ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
PostgreSQL / BigQuery 中的 QUALIFY 简化写法
如果用的是支持 QUALIFY 的引擎(如 BigQuery、Snowflake、PostgreSQL 14+),可以省掉嵌套子查询:
SELECT * FROM orders QUALIFY ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at) = 1;
-
QUALIFY是窗口函数的“后置 WHERE”,专为这类场景设计 - 比子查询更简洁,但不是所有数据库支持;执行计划上通常和子查询等价
- 别在
QUALIFY里用FIRST_VALUE做条件判断——它不能用于过滤,只返回值
真正要拿“组内第一条记录”,核心永远是排序 + 编号 + 筛选。FIRST_VALUE 是观察者,不是筛选器。窗口函数的语义边界,比语法更容易被忽略。











