FIRST_VALUE取排序后窗口首行值,须配合OVER和ORDER BY;默认窗口为UNBOUNDED PRECEDING到CURRENT ROW;常用于分组标注首值,非过滤行数。

FIRST_VALUE函数的基本用法和排序依赖
FIRST_VALUE 不是“取表里第一条”,而是“按 ORDER BY 排序后取当前窗口帧的第一条”。如果漏写 ORDER BY,结果不可预测,多数数据库(如 PostgreSQL、SQL Server)会报错,MySQL 8.0+ 则强制要求。它必须配合 OVER 子句,且排序逻辑直接决定哪条是“首个”。
- 必须显式指定
ORDER BY,例如ORDER BY created_at或ORDER BY id - 若想取时间最早的一条,就按时间升序;取 ID 最小的一条,就按
id ASC - 窗口范围默认是
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,这意味着它只看“从组开头到当前行”,所以当前行位置会影响结果——除非你用ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING或干脆不指定(依赖默认行为)
按分组取每组第一条记录的典型写法
常见需求是“每个 category 下最新的一条订单”,这时要结合 PARTITION BY 和合适排序:
SELECT
category,
order_id,
amount,
FIRST_VALUE(order_id) OVER (
PARTITION BY category
ORDER BY created_at DESC
) AS latest_order_id
FROM orders;-
PARTITION BY category把数据按分类切块 -
ORDER BY created_at DESC让最新记录排第一,FIRST_VALUE才能取到它 - 注意:这不会过滤出“唯一一条”,只是给每行打上该组的“首个值”;如需真正只留每组一行,得套一层
ROW_NUMBER()或用QUALIFY(BigQuery / Snowflake)或子查询
容易被忽略的 NULL 处理和性能陷阱
FIRST_VALUE 遇到 NULL 值时照常参与排序——也就是说,如果 ORDER BY 字段有 NULL,它们可能被排在最前或最后(取决于数据库的 NULLS FIRST/LAST 默认),导致误取。
- PostgreSQL 默认
NULLS LAST(升序时),SQL Server 把NULL当最小值处理 - 显式控制更安全:加
NULLS LAST或NULLS FIRST,例如ORDER BY status NULLS LAST, updated_at DESC - 性能上,
FIRST_VALUE需要完整扫描窗口内所有行,若PARTITION BY组极大(如单个用户几万条日志),又没在PARTITION BY + ORDER BY字段建联合索引,执行会明显变慢
替代方案:什么时候不该硬用 FIRST_VALUE?
当目标只是“取出每组一条代表记录”,FIRST_VALUE 往往不是最简路径:
- 如果只要结果集里每组一条,
ROW_NUMBER() = 1更直观、兼容性更好 - 在支持
QUALIFY的引擎(如 BigQuery、Snowflake)中,QUALIFY ROW_NUMBER() OVER (...) = 1可直接过滤 - MySQL 8.0+ 虽支持
FIRST_VALUE,但早期版本只能靠变量或自连接模拟,维护成本高
真正需要 FIRST_VALUE 的场景,是保留原行其他字段的同时,“附带标注”该组首个值——比如展示全部订单,并标出每个客户首笔订单金额。这时候改用聚合或子查询反而会破坏行粒度。
窗口函数的语义边界很清晰:它不改变行数,只增列。别指望它代替 GROUP BY 去压缩数据。

















