FIRST_VALUE是窗口函数,必须配合OVER(PARTITION BY... ORDER BY...)使用,ORDER BY需显式指定ASC方向;它不聚合行数,而是为每行返回组内首值;聚合用MIN+GROUP BY,取完整首记录用ROW_NUMBER();MySQL 5.7以下不支持,需子查询替代。

FIRST_VALUE必须搭配窗口函数语法才能生效
单独写 FIRST_VALUE(reg_time) 会报错,因为它是窗口函数,不支持在普通 SELECT 中直接调用。必须配合 OVER() 子句,并明确指定分组(PARTITION BY)和排序(ORDER BY)逻辑。
常见错误是漏掉 ORDER BY —— 比如只写 OVER (PARTITION BY dept_id),此时数据库无法确定“最早”依据什么排序,结果不可靠(某些引擎可能返回任意一行,甚至报错)。
- 必须写成
FIRST_VALUE(reg_time) OVER (PARTITION BY dept_id ORDER BY reg_time) - 如果时间字段含时分秒且需严格按注册顺序,确保
reg_time是TIMESTAMP或DATETIME类型,不是字符串 - 注意 NULL 值:默认
FIRST_VALUE会跳过 NULL(除非显式加RESPECT NULLS,但获取“最早时间”场景通常不希望 NULL 被选中)
要查“各组最早注册时间”,别直接 SELECT FIRST_VALUE 结果
很多人误以为 SELECT dept_id, FIRST_VALUE(reg_time) OVER (...) AS first_reg 就能拿到每组一个值,其实它会为组内每一行都重复输出同一个最早时间,导致结果行数不变——这不是聚合,而是窗口计算。
真正要“每组一行 + 最早时间”,得用 GROUP BY 配合 MIN(reg_time);而 FIRST_VALUE 的典型用途是:在保留原始明细行的同时,把该组最早时间“广播”到每行做对比或标记。
- 想聚合出每组一行 → 用
SELECT dept_id, MIN(reg_time) FROM users GROUP BY dept_id - 想在用户明细表里加一列“本部门最早注册时间” → 才用
FIRST_VALUE(reg_time) OVER (PARTITION BY dept_id ORDER BY reg_time) - 若需去重只取最早那条完整记录(比如最早注册用户的姓名+时间),应结合
ROW_NUMBER() OVER (... ORDER BY reg_time)过滤rn = 1
ORDER BY 方向影响结果,ASC 是默认但建议显式写出
FIRST_VALUE 总是取排序后第一行的值,所以 ORDER BY reg_time ASC 得最早时间,DESC 则得最晚时间。虽然 ASC 是默认行为,但不同数据库对未声明方向的处理略有差异(例如某些版本 PostgreSQL 在无方向时可能警告),且可读性差。
- 务必写成
ORDER BY reg_time ASC,避免歧义 - 如果存在并列最早时间(相同
reg_time),FIRST_VALUE只返回其中一行(具体哪行由底层实现决定,不可预测),如需稳定结果,追加唯一排序键:ORDER BY reg_time ASC, user_id ASC - 性能上,该操作依赖排序,大数据量时注意
reg_time字段是否有索引,尤其当PARTITION BY字段区分度低(比如只有几个大组)时,排序开销明显
MySQL 8.0+ 和 PostgreSQL 支持直接使用,旧版 MySQL 需绕行
MySQL 5.7 及更早版本不支持窗口函数,强行写 FIRST_VALUE 会报错 FUNCTION xxx.FIRST_VALUE does not exist。此时只能用关联子查询或变量模拟:
SELECT u1.dept_id, u1.user_name, u1.reg_time,
(SELECT MIN(u2.reg_time) FROM users u2 WHERE u2.dept_id = u1.dept_id) AS first_reg
FROM users u1;
PostgreSQL 和 MySQL 8.0+ 可直接用,但注意 PostgreSQL 中 FIRST_VALUE 默认 RESPECT NULLS,而 MySQL 默认 IGNORE NULLS —— 如果数据中 reg_time 可能为空,行为不一致,建议统一加 IGNORE NULLS 显式声明。
跨数据库移植时,这个细节容易被忽略,导致测试环境正常、生产环境结果异常。










