FIRST_VALUE()默认窗口帧为UNBOUNDED PRECEDING TO CURRENT ROW且不自动分组,必须显式指定PARTITION BY和ORDER BY才能准确获取分组首行值;漏写ORDER BY会导致结果不可预测,仅写PARTITION BY行为未定义。

为什么FIRST_VALUE()返回的不是分组第一行?
因为FIRST_VALUE()默认窗口范围是UNBOUNDED PRECEDING TO CURRENT ROW,它不自动按分组切片——你必须显式定义PARTITION BY和ORDER BY。漏写ORDER BY会导致结果不可预测(多数数据库返回任意一行),而只写PARTITION BY不排序,行为未定义。
正确写法:PARTITION BY + ORDER BY 缺一不可
要获取每个分组内按某字段排序后的首行值,必须同时指定分组键和排序依据:
SELECT
category,
product_name,
price,
FIRST_VALUE(product_name) OVER (
PARTITION BY category
ORDER BY price ASC
) AS cheapest_in_category
FROM products;
-
PARTITION BY category确保每个类别独立计算 -
ORDER BY price ASC决定“首行”是谁——这里取最便宜的 - 若想取最新上架的,就改用
ORDER BY created_at DESC - 注意:
FIRST_VALUE()返回的是该行对应列的值,不是整行;如需多列,得对每列单独调用
常见错误:NULL值、重复排序键、窗口帧误用
以下情况容易导致结果异常:
- 排序字段含
NULL:不同数据库处理方式不同(PostgreSQL默认NULLS LAST,MySQL 8.0默认NULLS FIRST),建议显式写ORDER BY price ASC NULLS LAST - 多个产品价格相同:
FIRST_VALUE()返回其中一行,但具体哪行由底层实现或额外排序决定;如需稳定结果,加二级排序,例如ORDER BY price ASC, product_id ASC - 误加
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING:虽然语法合法,但会破坏“首行”的语义——它让窗口包含全部行,FIRST_VALUE()仍返回排序后第一行,但性能更差,且易与LAST_VALUE()混淆
替代方案:当FIRST_VALUE()不够用时
如果需要整行数据(不止一个字段),或逻辑更复杂(比如“首行且满足条件”),FIRST_VALUE()就不够用了:
- 取完整首行记录:用
ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) = 1过滤 - 兼容老版本MySQL(
- 需要动态判断“首行条件”(如首个status='active'的记录):
FIRST_VALUE()做不到,得结合CASE WHEN和排序,或改用LAG()/LEAD()辅助判断
真正难的不是写对语法,而是想清楚“首行”的业务定义是否稳定、可排序、无歧义——一旦排序依据本身有歧义,再正确的FIRST_VALUE()也救不了结果。











