first_value返回每组排序后首值并填充到所有行,不减少行数;需配合row_number()筛选序号为1的行才能获取完整代表记录。

为什么FIRST_VALUE返回的不是“每组第一条记录”?
直接用FIRST_VALUE窗口函数,常会发现结果和预期不符——它默认按当前窗口排序后取第一个值,但**不改变原始行数**,每行都补一个“首值”,而不是只留一行。你真正想要的,通常是“每组保留一条代表记录”,比如最新订单、最早登录、最高分学生。这时候FIRST_VALUE只是中间工具,不能单独解决“取整条记录”的问题。
怎么用FIRST_VALUE配合ROW_NUMBER拿到完整记录?
核心思路:先用ROW_NUMBER()给每组内记录编号,再用WHERE筛选出序号为1的行。此时FIRST_VALUE可作为验证或辅助字段(比如同时显示该组首值但不依赖它过滤)。
-
ORDER BY必须明确写在OVER子句里,否则ROW_NUMBER顺序不确定,结果不可靠 - 分组字段(如
user_id)要写在PARTITION BY中,且和GROUP BY逻辑一致 - 若需“最后一条”,把
ORDER BY created_at DESC改成ASC,或直接用LAST_VALUE(但要注意LAST_VALUE默认窗口范围是ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,得显式指定RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING才安全)
示例(取每个用户的最新订单):
SELECT order_id, user_id, amount, created_at
FROM (
SELECT order_id, user_id, amount, created_at,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders
) t
WHERE rn = 1;
FIRST_VALUE在SELECT里直接用有哪些陷阱?
它本身不减少行数,只做“复制粘贴式填充”。常见误用场景:
- 想查“每组金额最高的订单号”,却只写
FIRST_VALUE(order_id) OVER (PARTITION BY user_id ORDER BY amount DESC)→ 结果每行都带同一个order_id,但无法定位到对应那行的amount或created_at - 没写
ORDER BY,数据库按物理存储顺序排,不同执行可能结果不同 - 在聚合查询(含
GROUP BY)里混用FIRST_VALUE,会报错或语义混乱——窗口函数和聚合不能直接共存于同一层SELECT
正确做法:要么用子查询/CTE先算好窗口值,再关联;要么坚持用ROW_NUMBER+过滤,更直观可控。
MySQL 8.0+ 和 PostgreSQL 的行为差异要点
两者都支持FIRST_VALUE和ROW_NUMBER,但细节影响实操:
- MySQL 8.0+ 对
ORDER BY中的表达式要求更严,比如不能直接用别名(如ORDER BY dt),得写原字段或列位置 - PostgreSQL 允许在
ORDER BY里用NULLS FIRST/LAST,处理空值更灵活;MySQL 目前不支持该语法 - SQL Server 的
TOP 1 WITH TIES配合ORDER BY ... OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY在某些场景下比窗口函数更简短,但不具备跨组能力
跨数据库移植时,优先用ROW_NUMBER方案,兼容性最好;FIRST_VALUE建议只用于生成派生字段,别用来驱动逻辑分支。










