FIRST_VALUE是窗口函数,必须配合OVER(PARTITION BY...ORDER BY...)使用,不可与GROUP BY混用;它为每行返回分组内按排序确定的首值,不减少行数,需结合ROW_NUMBER()=1才能真正取单行。

FIRST_VALUE 本质是窗口函数,不是分组聚合函数
FIRST_VALUE 不会自动按 GROUP BY 分组;它必须配合 OVER 子句定义窗口范围,否则报错或返回意外结果。常见错误是写成 SELECT FIRST_VALUE(col) FROM t GROUP BY group_col——这语法不合法,SQL 引擎直接拒绝执行。
正确思路是:用 OVER (PARTITION BY ... ORDER BY ...) 划定“每个分组内按某列排序后的首行”,然后在 SELECT 中直接引用该计算列。它不减少行数,而是为每行补一个“本组首值”。
- 必须显式写
ORDER BY,否则行为未定义(不同数据库可能返回任意一行) -
PARTITION BY定义分组维度,等价于GROUP BY的逻辑分组,但不压缩行 - 若想只取每组一行,得额外用
ROW_NUMBER() = 1过滤,不能只靠FIRST_VALUE
取分组内首行数据的典型写法(带排序依据)
假设表 sales 有字段 region、sale_date、amount,要取每个 region 下最早一笔销售的 amount 值,并附在每行上:
SELECT
region,
sale_date,
amount,
FIRST_VALUE(amount) OVER (
PARTITION BY region
ORDER BY sale_date ASC
) AS first_amount_in_region
FROM sales;
注意:ORDER BY sale_date ASC 决定了“首行”是谁;如果日期相同,可能有多个“首行”,此时 FIRST_VALUE 返回其中一值(具体哪行由底层实现决定,不可控)。
- 若需稳定结果,应在
ORDER BY中加入唯一列,如ORDER BY sale_date, id - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;SQLite 仅从 3.25.0 开始支持
- 性能上,窗口函数需全量扫描并排序,大数据量时注意索引覆盖
PARTITION BY + ORDER BY字段
只想保留每组一行?别只用 FIRST_VALUE
FIRST_VALUE 本身不筛选行,它只是复制值。如果目标是“每个分组只返回一条记录(即真正意义上的首行)”,必须结合行号过滤:
SELECT region, sale_date, amount
FROM (
SELECT
region,
sale_date,
amount,
ROW_NUMBER() OVER (
PARTITION BY region
ORDER BY sale_date, id
) AS rn
FROM sales
) t
WHERE rn = 1;
这是更常用也更可控的做法。相比 FIRST_VALUE,它明确剔除冗余行,且避免了因重复排序键导致的不确定性。
-
ROW_NUMBER()保证每行唯一编号,RANK()或DENSE_RANK()在并列时会跳号或不跳号,不适合“取首行”场景 - 若业务允许并列首行都保留,可用
RANK() = 1替代ROW_NUMBER() = 1 - 某些旧版数据库不支持子查询中用窗口函数,需改用 CTE(
WITH子句)
NULL 值和默认值处理容易被忽略
当分组内所有 ORDER BY 字段都为 NULL,或整个分组为空时,FIRST_VALUE 可能返回 NULL。部分数据库(如 PostgreSQL)允许指定 NULLS FIRST 或 NULLS LAST 显式控制 NULL 排序位置:
FIRST_VALUE(amount) OVER ( PARTITION BY region ORDER BY sale_date NULLS LAST )
但要注意:NULLS FIRST/LAST 并非所有数据库都支持(MySQL 目前不支持该语法),且它影响的是排序顺序,不改变 FIRST_VALUE 对空值本身的处理逻辑。
- 如果首行对应字段本身就是
NULL,FIRST_VALUE就返回NULL,不会跳过 - 没有内置的“跳过 NULL 取首个非 NULL”的选项,需用
CASE WHEN配合其他逻辑预处理 - 测试时务必构造含
NULL的分组数据,否则上线后可能意外返回全NULL结果

















