首页/数据/SQL 速查/FIRST_VALUE 组内首值
聚合与窗口函数54 条

FIRST_VALUE 组内首值

取窗口帧内首行的值:把“组内最早/排在首位”的字段广播到每一行,用于对比与填充。

SQL 速查数据更新于 2026-10-13

FIRST_VALUE 做什么

FIRST_VALUE(表达式) OVER (…) 取窗口帧内第一行的值,并把它广播到该分区的每一行。常用于「把组内最早或最靠前的记录字段带到本组每一行」做对比、填充或算差值。MySQL 8.0 与 PostgreSQL 8.4+ 均支持,同族还有 LAST_VALUE 与 NTH_VALUE。

基本信息

适用平台MySQL 8.0+ / PostgreSQL 8.4+(窗口函数)
语法FIRST_VALUE(expr) OVER (PARTITION BY … ORDER BY …)
返回值帧内首行的值;帧为空时为 NULL
同族函数LAST_VALUE、NTH_VALUE

典型使用场景

  • 首单时间:把每个用户第一笔订单时间广播到该用户所有订单行
  • 起点对比:用当前行与组内首行算差值、判断是否首单
  • 报表展平:分组取首行字段当作组属性,避免再 JOIN 一次
  • 状态回填:用组内初始状态填充后续缺失字段
  • 配合 DISTINCT 或行号取每组第一条明细

示例

示例
SELECT user_id, order_id, created_at,
       ROW_NUMBER() OVER w AS rn,                     -- 组内序号,从 1 开始
       FIRST_VALUE(created_at) OVER w AS first_at,     -- 组内首行时间,每行都一样
       LAST_VALUE(created_at) OVER w  AS last_at_now   -- 默认帧下等于当前行,见下表
FROM orders
WINDOW w AS (PARTITION BY user_id ORDER BY created_at);

同族函数与陷阱

函数取值位置默认帧下的常见误解
FIRST_VALUE帧内第一行加了 ROWS 收窄帧后取的是帧首行,不再是分区首行
LAST_VALUE帧内最后一行默认帧到当前行,结果往往就是当前行
NTH_VALUE(x, n)帧内第 n 行帧内行数不足 n 时结果为 NULL
默认帧是 RANGE UNBOUNDED PRECEDING AND CURRENT ROW,所以 FIRST_VALUE 取分区首行、LAST_VALUE 取当前行。要取分区最后一行需显式写 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。
排序键有并列值时「首行」在并列组内不确定,结果可能随执行计划变化;补一个唯一的次级排序键(如主键)保证稳定。FIRST_VALUE 也不会跳过 NULL,首行是 NULL 时整组就广播 NULL。
MIN() 与 FIRST_VALUE 的区别:MIN 返回组内最小的那个值,FIRST_VALUE 返回排序后首行的取值。需要「时间最早的那一整条记录」时用 FIRST_VALUE 或行号筛选更直接,不必先用 MIN 再回表匹配。

易错点

  • LAST_VALUE 在默认帧下等于当前行,误以为能取组内最后一个值;要取组末需显式写 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  • 排序键有并列值时首行不确定,同一份 SQL 两次执行结果可能不同;补唯一次级排序键
  • 自加 ROWS 帧后 FIRST_VALUE 取的是帧首行而非分区首行,想取组内首值就别收窄帧起点
  • 首行本身是 NULL 时整个分区的广播值都是 NULL,需先过滤或用 COALESCE 兜底
  • MySQL 5.7 及更早版本不支持窗口函数,直接执行会语法报错,需升级或用自连接模拟
  • 用 MIN(时间) 再回表取整条记录:遇到并列时间会匹配到多条或漏掉业务上真正要的那条,改取首行更稳

数据来源与参考

  • MySQL 8.0 Reference Manual · FIRST_VALUE
  • PostgreSQL Documentation · Window Functions
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌