first_value是窗口函数,必须配合over()使用,不能与group by混用;正确写法需指定partition by分组、order by排序及rows between unbounded preceding and unbounded following窗口帧,才能准确获取分组首行值。

FIRST_VALUE 是窗口函数,不是聚合函数
直接在 GROUP BY 查询里用 FIRST_VALUE 会报错或返回意外结果,因为它必须配合 OVER() 子句使用,且不折叠行数。常见错误是写成:SELECT dept, FIRST_VALUE(salary) FROM emp GROUP BY dept —— 这语法非法,FIRST_VALUE 不接受无 OVER 的调用。
真正用途是:在保留原表所有行的前提下,为每行打上“本组第一条的某个字段值”。比如想给每个员工标出其所在部门薪资最高者的姓名,就得靠它。
-
FIRST_VALUE必须搭配OVER (PARTITION BY ... ORDER BY ...) - 默认窗口帧是
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,这意味着它只看当前行及之前行 —— 如果你想要真·分组首条(即整个分区第一条),得显式加ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - ORDER BY 很关键:没它,
FIRST_VALUE行为未定义;顺序错,取到的就不是你认为的“首条”
正确写法:PARTITION BY + 显式窗口帧
假设要按部门取入职时间最早的员工姓名(即每个部门第一条记录的 name),SQL 应该这样写:
SELECT
name,
dept,
hire_date,
FIRST_VALUE(name) OVER (
PARTITION BY dept
ORDER BY hire_date
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS first_hired_name
FROM emp;
注意三点:
-
PARTITION BY dept划分部门组 -
ORDER BY hire_date确保“最早入职”被排在最前 -
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING才能确保取到整个分区的第一条,否则默认帧会导致中间行取不到真正的首条
和 LIMIT 1 + GROUP BY 混用的误区
有人试图用子查询先 GROUP BY dept 再 LIMIT 1 来模拟“首条”,这是错的:SQL 标准不保证 GROUP BY 后哪一行被选中,MySQL 5.7+ 严格模式下甚至直接报错 Expression #1 of SELECT list is not in GROUP BY clause。
更安全的做法是用窗口函数后外层过滤:
SELECT name, dept, hire_date
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY hire_date) AS rn
FROM emp
) t
WHERE rn = 1;
这个方案比 FIRST_VALUE 多一步但更可控 —— 尤其当你需要整行数据(不止一个字段)时,FIRST_VALUE 只能逐字段写,而 ROW_NUMBER() 配合 WHERE 能直接拿到完整首行。
不同数据库对 FIRST_VALUE 的兼容性细节
PostgreSQL、SQL Server、Oracle、BigQuery 都支持标准语法;MySQL 从 8.0 开始支持,但早期版本(如 5.7)完全不可用,会报错 FUNCTION xxx.FIRST_VALUE does not exist。
- SQLite 直到 3.25.0(2018年)才加入窗口函数,旧版无效
- 某些数据库(如 Redshift)虽支持
FIRST_VALUE,但不支持自定义窗口帧,强制使用默认帧 —— 此时若依赖“整个分区首条”,结果可能出错 - 如果 ORDER BY 字段有重复值(比如多个员工同一天入职),
FIRST_VALUE返回的是这些并列行中的任意一个(稳定但不可预测),需额外加ORDER BY hire_date, id消除歧义
真正难的不是写对语法,而是想清楚:你要的“首条”,是逻辑排序后的第一个,还是物理插入顺序的第一个?后者 SQL 本身无法保证,除非表有明确的时间戳或自增主键参与排序。











