用ROW_NUMBER()取组内最小值所在行,需PARTITION BY分组、ORDER BY目标字段ASC升序排列后取rn=1;并列时ROW_NUMBER()仅返回一行,RANK()可返回所有最小值行。

用 ROW_NUMBER() 取组内最小值所在行,关键在排序逻辑
直接结论:ROW_NUMBER() 本身不判断“最小值”,它只按你给的 ORDER BY 排序后标号;想拿到最小值那行,必须把要比较的字段(比如 price)放进 ORDER BY 子句里升序排,再取 rn = 1 的行。
常见错误是写成 ORDER BY id 或漏掉 PARTITION BY,结果每组只随机返回一行,跟最小值完全无关。
- 必须写
PARTITION BY group_col划分组,否则全表当一组 -
ORDER BY target_col ASC才能保证最小值排第一;写DESC就拿到最大值了 - 如果有并列最小值,
ROW_NUMBER()会强制拆成 1、2、3…,不保证稳定;要保留所有并列项得换RANK()或DENSE_RANK()
带并列情况时,ROW_NUMBER() 和 RANK() 的行为差异
当组内多个行的 score 都等于最小值(比如都是 5),ROW_NUMBER() 会给它们分配不同序号(如 1、2、3),只有一行能被 rn = 1 捕获;而 RANK() 会让它们都得 1,后续跳过编号(比如下一个是 4)。
所以:要严格取“一行”(哪怕有并列也只认一个),用 ROW_NUMBER();要“所有最小值行”,必须用 RANK() 并筛选 rn = 1。
-
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY score ASC)→ 并列时只保一条,顺序不确定 -
RANK() OVER (PARTITION BY dept ORDER BY score ASC)→ 并列都得 1,适合取全部最小值行 - 如果还关心稳定性(比如相同
score时按id再排),写成ORDER BY score ASC, id ASC
性能注意:WHERE 条件不能直接下推到窗口函数里
窗口函数是在 WHERE 和 GROUP BY 之后执行的,所以不能在 WHERE 里写 rn = 1 —— 会报错 column "rn" does not exist。必须用子查询或 CTE 包一层。
- 错的写法:
SELECT *, ROW_NUMBER() OVER (...) AS rn FROM t WHERE rn = 1 - 对的写法:用 CTE:
WITH ranked AS (SELECT *, ROW_NUMBER() OVER (...) AS rn FROM t) SELECT * FROM ranked WHERE rn = 1 - 大表慎用:
ROW_NUMBER()需扫描全组数据,没索引加速的话,PARTITION BY字段 +ORDER BY字段最好有联合索引
MySQL 8.0+、PostgreSQL、SQL Server 都支持,但 SQLite 不行
SQLite 直到 3.25.0(2018年)才支持窗口函数,旧版直接报错 no such function: ROW_NUMBER。如果必须兼容老 SQLite,只能用关联子查询或自连接模拟,性能差很多。
- 确认版本:
SELECT sqlite_version();,低于 3.25.0 就别硬上ROW_NUMBER() - PostgreSQL 和 SQL Server 对
NULL的排序默认是NULLS FIRST,如果最小值可能是NULL,要显式写ORDER BY col ASC NULLS LAST避免误取 - MySQL 8.0 默认
NULL最小,和 PostgreSQL 行为不一致,跨库迁移时容易出错
实际用的时候,最容易被忽略的是排序字段的 NULL 处理方式和并列值的语义选择——不是语法写对就行,得看业务到底要“任一最小值行”还是“全部最小值行”,这个一旦定错,线上查出来的数据就 quietly 错了。

















