因为first_value和last_value默认窗口帧为rows between unbounded preceding and current row,last_value仅作用于当前行及之前行,故常返回当前行值而非分组末尾值;必须显式指定rows between unbounded preceding and unbounded following并配合确定order by才能取整组首尾值。

为什么FIRST_VALUE和LAST_VALUE返回的不是你想要的“首尾记录”?
直接用 FIRST_VALUE(col) 或 LAST_VALUE(col) 很可能拿不到分组内真实的第一行或最后一行数据——因为默认窗口框架是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,LAST_VALUE 在这个框架下只看到当前行及之前行,根本看不到组内最后一行。
常见错误现象:对同一组数据多次调用 LAST_VALUE,结果全是一样的(等于当前行值),而不是该组末尾那条记录的值。
- 必须显式声明窗口框架为
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING -
ORDER BY子句不可省略,否则行为未定义(即使你只关心“物理首尾”,SQL标准也不保证无序时的顺序) - 如果想按时间取最新/最旧记录,
ORDER BY created_at和ORDER BY created_at DESC要配对使用:前者让FIRST_VALUE拿最早,后者让它拿最新
如何正确写一个带首尾值的分组查询?
假设有一张订单表 orders,按 user_id 分组,想拿到每人第一单金额、最后一单金额、以及对应订单ID:
SELECT user_id, FIRST_VALUE(amount) OVER (PARTITION BY user_id ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS first_amount, FIRST_VALUE(order_id) OVER (PARTITION BY user_id ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS first_order_id, LAST_VALUE(amount) OVER (PARTITION BY user_id ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_amount, LAST_VALUE(order_id) OVER (PARTITION BY user_id ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_order_id FROM orders;
注意:FIRST_VALUE 和 LAST_VALUE 的 ORDER BY 必须一致,否则逻辑错乱;如果想按金额排序取最高/最低,就改 ORDER BY amount,但要清楚这和“时间首尾”是两回事。
遇到NULL值或重复排序键怎么办?
当 ORDER BY 字段存在重复值(比如多个订单同秒创建),FIRST_VALUE 和 LAST_VALUE 可能返回任意一个匹配行的值——SQL不保证稳定性。
- 解决办法:在
ORDER BY后追加唯一字段,例如ORDER BY created_at, order_id - 如果源字段本身含NULL,
ORDER BY col默认把NULL排在最前(ASC)或最后(DESC),影响首尾判定;必要时用ORDER BY COALESCE(col, '1970-01-01')显式控制 - PostgreSQL 支持
NULLS FIRST / NULLS LAST,MySQL 8.0+ 和 SQL Server 也支持,但 SQLite 不支持——跨数据库时要注意兼容性
性能上要注意什么?
FIRST_VALUE 和 LAST_VALUE 是窗口函数,执行时需对每个分组做完整扫描,不能靠索引跳过中间行。
- 如果只想要首尾两条记录(而非每行都附带首尾值),用
ROW_NUMBER()+ 条件过滤通常更快:WHERE rn = 1 OR rn = cnt - 确保
PARTITION BY和ORDER BY字段上有联合索引,例如(user_id, created_at, order_id) - 在大数据量场景下,
LAST_VALUE配合UNBOUNDED FOLLOWING的开销和FIRST_VALUE相当,别以为它更重
真正容易被忽略的点是:窗口框架不写死,不同数据库默认行为不同;哪怕语法通过,结果也可能因隐式框架而错得悄无声息。











