row_number() 是最稳的分组取首行解法,需在子查询或cte中按partition by分组、order by排序,外层筛选rn=1;group by配min(id)易导致数据错乱,且无order by时顺序不保证;须建联合索引覆盖分组与排序字段,并注意数据库版本兼容性。

用 ROW_NUMBER() 给每组编号再过滤,是最稳的解法
直接在子查询或 CTE 里套一层 ROW_NUMBER(),按分组字段 PARTITION BY,再按业务逻辑(比如时间、ID)ORDER BY,最后外层查 rn = 1。别信“用 GROUP BY 配 MIN(id) 就能取第一条”——那只是取了某个聚合值,对应行的数据大概率错乱。
ROW_NUMBER() 的 ORDER BY 决定“第一条”是谁
同一组内顺序不明确,结果就不可靠。哪怕表有自增主键,SQL 标准也不保证无 ORDER BY 时的返回顺序。
- 想取最新一条:用
ORDER BY created_at DESC,不是ASC - 时间相同还要保确定性:加个二级排序,比如
ORDER BY created_at DESC, id DESC - Oracle/PostgreSQL 支持
NULLS LAST,MySQL 8.0+ 也支持;老版本 MySQL 要用IS NULL补偿
不同数据库对 ROW_NUMBER() 的兼容性差异
MySQL 5.7 不支持窗口函数,硬上会报错 ERROR 1064;SQL Server 2005+、PostgreSQL 8.4+、Oracle 8i+ 都行。如果必须兼容旧版 MySQL,得用关联子查询或变量模拟,但性能差、并发不安全。
- MySQL 8.0+ 和 PostgreSQL 可直接写:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY updated_at DESC) - SQL Server 注意:不能在
WHERE里直接用窗口函数别名,必须嵌套一层 - SQLite 3.25+ 支持,但 3.24 及更早版本不行
容易被忽略的性能坑:没给 PARTITION BY + ORDER BY 字段建联合索引
数据库执行 ROW_NUMBER() 时,会先按分组和排序字段做逻辑排序。如果没索引,大表可能触发磁盘临时表甚至 OOM。
- 索引要覆盖两个字段:比如
(category, created_at DESC),顺序不能反 - 如果
PARTITION BY a, b,那就建(a, b, order_col)联合索引 - PostgreSQL 中
ORDER BY ... NULLS LAST要求索引也声明NULLS LAST才能命中
ORDER BY 是否完整、索引是否跟得上、数据库版本是否兜得住。漏掉任意一环,查出来的就不是你要的那条。










