MySQL 8.0+ 使用 ROW_NUMBER() 最直接,按分组排序后为每行分配序号,再筛选所需行。

MySQL 8.0+ 用 ROW_NUMBER() 最直接
如果你用的是 MySQL 8.0 或更高版本,ROW_NUMBER() 是最直观、最可控的方式。它按分组排序后给每行打序号,再筛出
常见错误是漏写 PARTITION BY —— 不加这个就变成全表排序,不是“每组”Top 3;另外 ORDER BY 必须明确指定字段和方向(比如 score DESC),否则结果不稳定。
示例:查每个部门工资最高的前 3 人
SELECT dept, name, salary
FROM (
SELECT dept, name, salary,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn
FROM employees
) t
WHERE rn <= 3;
-
PARTITION BY dept确保编号在每个部门内独立重置 - 如果并列第 3 名不想被截断,改用
RANK()或DENSE_RANK() - 性能上,大表需确保
(dept, salary)有联合索引,否则OVER排序开销明显
PostgreSQL / SQL Server 同样适用 ROW_NUMBER()
PostgreSQL 8.4+ 和 SQL Server 2005+ 都支持标准窗口函数,写法和 MySQL 8.0+ 完全一致,无需额外适配。
唯一要注意的是 SQL Server 默认兼容级别可能低于 120,导致 ROW_NUMBER() 在某些旧模式下报错;检查并升级数据库兼容级别即可。
- PostgreSQL 中若字段含 NULL,默认排在最前,影响 Top 3 结果——可加
NULLS LAST显式控制 - SQL Server 如果用
TOP 3 WITH TIES替代窗口函数,只能实现全表 Top 3,不支持分组 - 避免在子查询里嵌套多层
ROW_NUMBER(),容易拖慢执行计划
MySQL 5.7 及更早版本只能靠自关联或变量模拟
老版本 MySQL 没有窗口函数,@rownum 变量方案看似简洁,但官方已明确警告:变量赋值顺序不保证,尤其在 ORDER BY 和 JOIN 共存时极易出错。
更稳妥的做法是自关联计数:对每条记录,统计同组中比它更大的记录数,小于 3 即入选。
SELECT e1.dept, e1.name, e1.salary FROM employees e1 WHERE ( SELECT COUNT(*) FROM employees e2 WHERE e2.dept = e1.dept AND e2.salary > e1.salary ) < 3;
- 这个写法逻辑清晰,但性能差——每行触发一次子查询,数据量过万就明显变慢
- 必须为
(dept, salary)建联合索引,否则子查询全表扫描 - 如果允许并列(比如两人同为第 3 名),上面的
要改成 <code> 并配合去重逻辑
Oracle 用 RANK() 处理并列更自然
Oracle 用户常遇到“第 3 名有两人并列,要不要都算进 Top 3?”这时 ROW_NUMBER() 会强制拆成 3 和 4,而 RANK() 给两人同标 3,下一个跳到 5,更符合业务语义。
注意 RANK() 和 DENSE_RANK() 的区别:前者会跳名次(1,1,3),后者不跳(1,1,2)。
- 用
RANK()时,WHERE rank_num 可能返回超过 3 行,这是预期行为,不是 bug - Oracle 12c+ 支持
FETCH FIRST 3 ROWS ONLY,但仅限全表 Top N,无法直接用于分组场景 - 别在
ORDER BY里混用函数(如UPPER(name))而不加确定性排序字段,会导致RANK()结果不可复现

















