FIRST_VALUE()必须配合OVER()使用,显式指定PARTITION BY和ORDER BY才能准确获取每组按时间或ID最小的首条记录,否则结果随机且不可控。

MySQL 8.0 中 FIRST_VALUE() 必须配合 OVER() 使用,不能单独求“分组第一条”
直接写 FIRST_VALUE(col) 不报错但结果不可控——它默认按无序行处理,所谓“第一”是随机的。真正拿到业务上想要的“每组按时间/ID最小的那条”,必须显式指定 ORDER BY 和 PARTITION BY。
常见错误现象:
– 查询结果每次执行不一致
– 同一分组里 FIRST_VALUE(name) 返回了中间某条而非最早录入的
– 忘记 PARTITION BY,整个表只算一个“组”
-
FIRST_VALUE()是窗口函数,必须嵌套在SELECT中,不能用于WHERE或GROUP BY - 若想按“创建时间最早”取第一条,
ORDER BY created_at ASC;若按“ID 最小”,则ORDER BY id ASC - 分组字段(如
category_id)必须写进PARTITION BY category_id,否则不分组
正确写法示例:查每个分类下创建时间最早的用户姓名
假设表 users 有字段 id、name、category_id、created_at:
SELECT
id,
name,
category_id,
created_at,
FIRST_VALUE(name) OVER (
PARTITION BY category_id
ORDER BY created_at ASC, id ASC
) AS first_user_in_category
FROM users;
注意这里加了 id ASC 作为第二排序键——避免 created_at 相同时结果不稳定。MySQL 8.0 窗口函数要求 ORDER BY 子句必须能唯一确定行序,否则“第一”的语义模糊。
FIRST_VALUE() 和子查询 / ROW_NUMBER() 的性能与语义差异
有人用 ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) = 1 过滤首行,也有人用关联子查询。三者语义不同:
-
FIRST_VALUE()是“复制值”:每行都带本组第一条的name,适合做参考列或比对 -
ROW_NUMBER() = 1是“筛选行”:只返回每组一行,适合聚合后取样 - 子查询(如
(SELECT name FROM users u2 WHERE u2.category_id = u1.category_id ORDER BY created_at LIMIT 1))在 MySQL 8.0 下通常比窗口函数慢,尤其数据量大时缺乏索引优化空间 - 窗口函数天然支持多层级排序和帧定义(如
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),但求“第一条”一般不需要
容易被忽略的 NULL 和排序陷阱
FIRST_VALUE() 遇到 NULL 值会照常参与排序——默认 NULLS FIRST(MySQL 行为),所以如果 created_at 允许为空,NULL 会被当作“最早时间”,导致取到错误记录。
- 稳妥做法:过滤掉空时间
WHERE created_at IS NOT NULL,或在ORDER BY中显式控制NULLS LAST(MySQL 8.0.22+ 支持) - 若业务上“未填写时间”应排在最后,写成
ORDER BY created_at ASC NULLS LAST - 字符串字段用
FIRST_VALUE()时要注意 collation,比如大小写敏感可能影响排序结果顺序
窗口函数不是黑盒,它的“第一”完全由你写的 PARTITION BY 和 ORDER BY 定义——少一个条件,结果就可能偏出预期。


















