最稳妥方式是用ROW_NUMBER()配合PARTITION BY和必需的ORDER BY,取rn=1;避免FIRST_VALUE/LAST_VALUE默认帧陷阱;注意数据库版本支持及排序稳定性。

用 ROW_NUMBER() 提取每组第一条记录最稳妥
直接用 ROW_NUMBER() 配合 PARTITION BY 是最通用、最可控的方式。它不依赖字段值是否可排序或唯一,只按指定顺序给每组内行编号,取 rn = 1 就是首条。
常见错误是漏写 ORDER BY 子句——ROW_NUMBER() 要求必须有明确排序依据,否则报错:window function ROW_NUMBER requires an ORDER BY clause。
- 若想取“最早时间”的记录,
ORDER BY created_at ASC - 若想取“最新更新”的记录,
ORDER BY updated_at DESC - 多个字段排序时注意优先级,比如
ORDER BY status DESC, id ASC先按状态降序,同状态再按 ID 升序 - 避免用
ORDER BY RAND()取随机首条——性能差,且结果不可复现
用 FIRST_VALUE() 和 LAST_VALUE() 有陷阱
这两个函数看起来更直白,但实际使用中容易返回重复值或意外结果,因为它们默认的窗口帧(frame)是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,不是整组。
比如 LAST_VALUE(name) 在默认帧下,最后一行才返回真正末尾值,其余行返回的是“当前行及之前行里的最后一个值”,不是整组的末尾值。
- 必须显式指定完整帧:
FIRST_VALUE(x) OVER (PARTITION BY y ORDER BY z ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) -
LAST_VALUE()同理,不加ROWS BETWEEN ...极大概率出错 - 如果只是要字段值,
FIRST_VALUE()可用;但如果要整行(含多个字段),它只能返回单个表达式,不如ROW_NUMBER()+ 子查询灵活
MySQL 8.0+ 和 PostgreSQL 的写法基本一致,但 SQLite 不支持
SQLite 直到 3.25.0(2018 年)才支持窗口函数,且部分发行版仍默认禁用。执行前务必确认:
- 运行
SELECT sqlite_version();,确保 ≥ 3.25.0 - 检查编译选项:
SELECT * FROM pragma_compile_options WHERE compile_options LIKE 'ENABLE_WINDOW_FUNCTIONS';,无结果说明不支持 - PostgreSQL 和 MySQL 8.0+ 对
ROW_NUMBER()行为一致,但 MySQL 5.7 及更早版本完全不支持,得用变量模拟(易出错,不推荐)
取“首尾两条”时别用两个 ROW_NUMBER() 分开算
有人会写两遍 ROW_NUMBER() OVER (PARTITION BY x ORDER BY y) 和 ROW_NUMBER() OVER (PARTITION BY x ORDER BY y DESC),再 WHERE rn1 = 1 OR rn2 = 1 —— 这在组内只有 1 行时会重复返回同一行。
更干净的做法是:用一次窗口计算,同时得到正序和逆序序号:
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at) AS rn_asc,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at DESC) AS rn_desc
FROM orders
) t
WHERE rn_asc = 1 OR rn_desc = 1;这样既能去重,又避免扫描两次窗口。
注意:如果分组内存在 created_at 相同的多行,ROW_NUMBER() 会任意排序,导致结果不稳定。此时应补一个确定性字段(如 id)作为第二排序键。

















