LAST_VALUE常返回“错的最后一条”是因为默认窗口帧仅覆盖当前行及之前行,必须显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING并配合确定性ORDER BY才能正确取分组末值。

用 FIRST_VALUE 和 LAST_VALUE 窗口函数取组内首尾值
直接用 FIRST_VALUE() 和 LAST_VALUE() 最稳妥,但必须配对 OVER() 子句,且默认框架(ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)会导致 LAST_VALUE() 不返回组末尾——这是最常踩的坑。
- 必须显式指定窗口帧:对组内全部行生效,写成
OVER (PARTITION BY group_col ORDER BY order_col ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) -
ORDER BY不可省略,即使你只关心“物理顺序”,也得用一个确定性列(如自增 ID 或时间戳),否则结果不可靠 - 若想按插入顺序取首尾,但表无序号列,需先用
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))生成伪序号(注意:SQL Server 支持,PostgreSQL 需用ctid或其他方式,MySQL 8.0+ 可用ROW_NUMBER())
GROUP BY + 聚合子查询替代方案(兼容老版本)
MySQL 5.7、SQLite 或不支持窗口函数的引擎里,FIRST_VALUE/LAST_VALUE 不可用,得靠关联子查询或连接。
- 取每组首个值(按 time 字段最小者):
SELECT t1.group_col,<br> (SELECT value FROM tbl t2 WHERE t2.group_col = t1.group_col ORDER BY t2.time LIMIT 1) AS first_val<br>FROM (SELECT DISTINCT group_col FROM tbl) t1
- 取末尾值(按 time 最大者):把
ORDER BY t2.time换成ORDER BY t2.time DESC,LIMIT 1保持不变 - 性能风险:子查询对每个分组执行一次,大数据量时明显变慢;有索引
(group_col, time)能缓解
用 MIN/MAX 配合对应行数据时的陷阱
MIN(time) 和 MAX(time) 只能拿到时间值,拿不到同一行的其他字段(比如 status、user_id)。强行用 GROUP BY + MIN() 会丢失关联信息。
- 错误写法:
SELECT group_col, MIN(time), status FROM tbl GROUP BY group_col——status值不确定,不是对应最早时间那行的值 - 正确思路:先用窗口函数标出首尾行(如
ROW_NUMBER() OVER (PARTITION BY group_col ORDER BY time) = 1),再过滤;或用上面的子查询方式 - PostgreSQL 可用
DISTINCT ON:SELECT DISTINCT ON (group_col) group_col, time, status FROM tbl ORDER BY group_col, time(首行),把time换成time DESC得末行
不同数据库对 LAST_VALUE 的默认行为差异
Oracle、SQL Server 默认 LAST_VALUE() 行为一致,但 PostgreSQL 和 MySQL 8.0+ 默认框架不同,容易误判。
- PostgreSQL:
LAST_VALUE(x) OVER (PARTITION BY g ORDER BY t)默认等价于ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,结果是“到当前行为止的最后一个”,不是整组最后一个 - MySQL 8.0+:同样遵循标准,默认帧不覆盖全组,必须显式加
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - 验证方法:在小样本上查
LAST_VALUE(x) OVER (...) AS lv, COUNT(*) OVER (...) AS cnt,看lv是否全组一致
ROWS BETWEEN ...;如果只是要时间字段的极值,MIN/MAX 更快,但要整行数据,窗口函数或子查询绕不开。

















