first_value()只能返回指定字段的首值,无法直接获取完整记录,因其本质是窗口函数而非行筛选工具,需对每个字段单独调用且确保over子句完全一致。

FIRST_VALUE 不能直接获取“一条记录”,它只返回某个字段的值。你在 Oracle 里写 FIRST_VALUE(name),得到的是 name 字段的首值,不是整行;想拿到该行的 id、created_at、status 等其他列,必须显式写出每个字段的 FIRST_VALUE(),且窗口定义要完全一致。
为什么直接用 FIRST_VALUE() 拿不到完整记录?
因为 FIRST_VALUE() 是窗口函数,不是行筛选工具。它默认行为是:对每组内排序后的第一行,把指定列的值“复制”到该组所有行上,行数不变。比如你查 100 条订单,FIRST_VALUE(order_id) OVER (PARTITION BY user_id ORDER BY created_at) 会返回 100 行,每行都带对应用户的最早 order_id,但你无法知道那条记录的 amount 或 product_id——除非你也写 FIRST_VALUE(amount) 和 FIRST_VALUE(product_id),并确保它们用完全相同的 OVER 子句。
- 常见误操作:
SELECT user_id, FIRST_VALUE(order_id) OVER w FROM orders WINDOW w AS (PARTITION BY user_id ORDER BY created_at)→ 只有order_id,其他字段全丢 - 正确做法(若坚持用
FIRST_VALUE):FIRST_VALUE(order_id) OVER w、FIRST_VALUE(amount) OVER w、FIRST_VALUE(created_at) OVER w全部并列写在 SELECT 中 - 窗口定义必须严格一致,否则不同字段可能取自不同行(尤其当
ORDER BY列有重复时)
如何用 FIRST_VALUE() 安全提取多个字段?
关键在 OVER 子句的稳定性:必须保证排序能唯一确定“第一条”。Oracle 默认窗口帧是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,这对 FIRST_VALUE() 不安全——它只看当前行及之前,不是整个分区。所以必须显式写全帧范围,并加固排序逻辑:
- 强制用
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,确保覆盖整组 -
ORDER BY至少含两个字段:主排序字段(如created_at)+ 唯一辅助字段(如id),避免并列时结果漂移 - 示例(取每用户最早订单的完整信息):
SELECT DISTINCT FIRST_VALUE(order_id) OVER w AS order_id, FIRST_VALUE(user_id) OVER w AS user_id, FIRST_VALUE(amount) OVER w AS amount, FIRST_VALUE(created_at) OVER w AS created_at FROM orders WINDOW w AS ( PARTITION BY user_id ORDER BY created_at ASC, id ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING );
为什么推荐优先用 ROW_NUMBER() 而不是 FIRST_VALUE()?
因为你要的是“每组一条记录”,本质是行过滤,不是字段填充。ROW_NUMBER() + WHERE rn = 1 更直观、可控、不易出错:
-
FIRST_VALUE()无法处理复杂条件,比如“跳过 status = 'cancelled' 的记录再取第一条”——你得先用CASE WHEN把无效行排到末尾,逻辑臃肿 -
ROW_NUMBER()可直接在ORDER BY里嵌套CASE:ORDER BY CASE WHEN status != 'cancelled' THEN 0 ELSE 1 END, created_at ASC, id - Oracle 对
DISTINCT+FIRST_VALUE()的行为不保证稳定(尤其未加ORDER BY时),而ROW_NUMBER()子查询结构清晰,调试方便 - 性能差异不大,但可读性和维护性高得多
真正容易被忽略的点是:**“第一条”的语义必须由 ORDER BY 定义清楚,而不是靠函数名幻想**。哪怕你用了 FIRST_VALUE(),如果 ORDER BY created_at 下有 5 条时间相同的数据,Oracle 可能每次返回不同行——除非你加上 id 或 rowid 作为确定性兜底。这个细节不写进排序,就永远得不到可复现的结果。











