first_value函数必须配合over子句使用,否则报错;需指定partition by和order by,且order by不可省略以保证结果确定性;不支持first_value(*),获取整行可用多字段first_value或row_number()过滤;性能优化依赖联合索引。

FIRST_VALUE函数必须配合窗口定义使用,单独用会报错
直接写 FIRST_VALUE(column_name) 会触发 ERROR 3589 (HY000): Window function 'FIRST_VALUE' is missing required OVER clause。MySQL 8.0 不允许脱离窗口上下文调用窗口函数,哪怕你只想要“组内第一条”,也得明确告诉它按什么分组、按什么排序。
常见错误写法:SELECT name, FIRST_VALUE(salary) FROM employees GROUP BY dept_id; —— 这既没 OVER,又混用了聚合和窗口函数,必然失败。
正确姿势是:先用 OVER(PARTITION BY ... ORDER BY ...) 定义逻辑窗口,再套函数:
SELECT dept_id, name, salary, FIRST_VALUE(name) OVER(PARTITION BY dept_id ORDER BY salary DESC) AS top_earner_in_dept FROM employees;
ORDER BY 在窗口定义中不可省略,否则结果不确定
FIRST_VALUE 的“第一”是相对排序而言的。不写 ORDER BY,MySQL 虽然语法上允许(如 OVER(PARTITION BY dept_id)),但实际返回哪一行完全取决于执行计划和数据物理存储顺序,每次查询可能不同,属于未定义行为。
务必显式指定排序依据,尤其注意 NULL 处理:
- 默认
NULLS FIRST(MySQL 8.0.22+ 支持该语法,旧版本始终把 NULL 排最前) - 若想让 NULL 排最后,写
ORDER BY salary DESC NULLS LAST - 业务上常需避免 NULL 干扰首条判断,可加
WHERE salary IS NOT NULL预过滤
获取整行记录时,不能直接用 FIRST_VALUE(*)
FIRST_VALUE 只接受单列表达式,不支持 FIRST_VALUE(*) 或 FIRST_VALUE(t.*)。想拿到“组内首条完整记录”的字段,有两个务实方案:
方案一:对每个需要的字段单独套 FIRST_VALUE:
SELECT dept_id, FIRST_VALUE(name) OVER w AS first_name, FIRST_VALUE(salary) OVER w AS first_salary, FIRST_VALUE(hire_date) OVER w AS first_hire FROM employees WINDOW w AS (PARTITION BY dept_id ORDER BY hire_date);
方案二:用 ROW_NUMBER() 标记序号后外层过滤(更直观,适合后续还要做其他排名操作):
SELECT dept_id, name, salary, hire_date
FROM (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY dept_id ORDER BY hire_date) AS rn
FROM employees
) t
WHERE rn = 1;
后者在需要“严格唯一首条”(比如有并列时只取一条)时更可控;前者在仅需几个字段且不想多嵌套时更轻量。
性能敏感场景要警惕窗口函数的临时表开销
窗口函数在 MySQL 中通常触发内部临时表(Using temporary; Using filesort),尤其当 PARTITION BY 基数高或 ORDER BY 字段无索引时,性能下降明显。
优化要点:
- 确保
PARTITION BY和ORDER BY组合字段上有联合索引,例如(dept_id, hire_date) - 避免在大表上对非选择性字段(如状态码只有 3 个值)做
PARTITION BY,会导致窗口数量爆炸 - 如果只是取每组 Top 1,且数据量极大,
ROW_NUMBER() + WHERE rn = 1有时比多个FIRST_VALUE更易被优化器利用索引
真正难处理的是既要取首条、又要保留原表所有行用于后续分析——这时窗口函数不可避免,索引和数据分布就是关键瓶颈点。











