MySQL 8.0+ 中直接用 ROW_NUMBER() 窗口函数即可实现组内排序,需版本≥8.0.2且未禁用窗口函数,语法为 OVER(PARTITION BY ... ORDER BY ...),推荐配合 CTE 使用以高效获取每组首行。

MySQL 8.0+ 用 ROW_NUMBER() 最直接
如果你用的是 MySQL 8.0 或更高版本,ROW_NUMBER() 是最干净的解法。它按分组排序后给每行打序号,再筛出 rn 即可。
注意:必须用子查询或 CTE 包一层,因为窗口函数不能直接在 WHERE 中引用;另外 ORDER BY 必须明确,否则序号不稳定。
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;常见错误:ORDER BY salary 写成 ORDER BY salary ASC 导致取了最低薪的三人;或者漏写 PARTITION BY dept,变成全表排前三。
PostgreSQL / SQL Server 同样适用 ROW_NUMBER()
语法完全一致,只是 PostgreSQL 对 NULL 处理更严格——如果 salary 可能为 NULL,ORDER BY salary DESC 会把 NULL 排最前(除非加 NULLS LAST)。SQL Server 则默认 NULLS LAST。
性能提示:这个写法依赖 (dept, salary) 联合索引效果最好;没索引时,大表可能触发临时表和文件排序。
使用场景:适合需要“严格唯一排名”的情况,比如工资、考试成绩这种不允许并列的业务逻辑。
旧版 MySQL(5.7 及以下)只能靠自关联或变量模拟
没有窗口函数时,得用相关子查询数“当前行比多少行高”,但性能差、易超时:
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;
要点:
- 条件用
而不是 <code>,因为 COUNT 是“比它高的数量” - 必须有
e2.dept = e1.dept,否则变成全表比较 - 如果存在相同 salary,这个方法会跳过并列者(比如 4 人同薪,只取前 3 行,但实际可能 4 人都该进前三)
另一个办法是用用户变量(@rank := @rank + 1),但 MySQL 5.7 中变量执行顺序不保证,结果可能错乱,线上环境慎用。
要保留并列名次?改用 RANK() 或 DENSE_RANK()
比如两个员工同为部门第一高薪,你希望他们都算第 1 名,后面那个是第 3 名(跳过 2),就用 RANK();如果希望是第 1、1、2 名,就用 DENSE_RANK()。
示例(MySQL 8.0+):
SELECT dept, name, salary,
RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rnk
FROM employees
WHERE rnk <= 3; -- 注意:这里不能直接 WHERE,仍需嵌套容易忽略的一点:用 RANK() 时,WHERE rnk 可能返回超过 3 行(比如 3 人并列第 1,1 人第 4),业务上是否允许必须提前确认。

















