row_number()需配合partition by按部门分组编号,order by必须在over内指定排序逻辑;漏写partition by会导致全表编号,窗口函数结果须用子查询或cte过滤。

用 ROW_NUMBER() 配合 PARTITION BY 按部门分组排序
ROW_NUMBER() 本身不支持直接“每组取前 N”,必须配合 PARTITION BY 把数据按部门切开,再在每个分区内独立编号。关键不是先排序再取,而是把排序逻辑写进 ORDER BY 子句里——它决定编号顺序,不是外部的 ORDER BY。
常见错误是漏写 PARTITION BY department,结果整个表只编一次号;或者把 ORDER BY salary DESC 写在窗口函数外,对编号毫无影响。
-
PARTITION BY department是必需的,否则所有员工混在一起编号 -
ORDER BY salary DESC必须写在ROW_NUMBER() OVER (...)的括号内 - 如果薪资相同,
ROW_NUMBER()会强制给出不同序号(比如并列第1会被标成1和2),这不是 bug,是它的定义
用子查询或 CTE 包裹才能 WHERE 过滤序号
窗口函数不能出现在 WHERE 子句中,所以不能直接写 WHERE rn 。必须先把带 <code>ROW_NUMBER() 的结果作为临时结果集,再在外面过滤。CTE 更清晰,子查询也行,但别试图在一层里搞定。
示例(PostgreSQL / SQL Server / Oracle 均适用):
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM employees
)
SELECT employee_id, name, department, salary
FROM ranked
WHERE rn
<p>注意:<code>rn</code> 是别名,必须在外部查询中引用;如果用子查询方式,记得给内层结果起别名(如 <code>AS ranked</code>),否则报错。</p>
<h3>ROW_NUMBER() vs RANK() vs DENSE_RANK():选哪个取决于并列怎么算</h3>
<p>如果两个员工薪资同为 15000 元且都是部门最高,<code>ROW_NUMBER()</code> 会给它们分配 1 和 2,跳过实际排名;<code>RANK()</code> 给 1 和 1,下一个是 3;<code>DENSE_RANK()</code> 给 1 和 1,下一个是 2。要“Top 3”意味着最多取 3 条记录,用 <code>ROW_NUMBER()</code> 最稳妥——它严格保证每组最多 3 行,不会因并列多拿。</p>
- 要严格限制条数(如每组只取 3 人)→ 用
ROW_NUMBER() - 要保留真实排名语义(并列即同名次)→ 用
RANK()或DENSE_RANK(),但得接受可能返回 >3 行 -
ORDER BY中可加第二排序字段(如ORDER BY salary DESC, employee_id ASC)来打破完全并列,让结果稳定
性能与索引建议:department + salary 组合索引很关键
当表很大时,PARTITION BY department ORDER BY salary DESC 会频繁扫描和排序。数据库无法利用单列索引高效完成这个操作,需要联合索引覆盖这两个字段。
- 建索引:
CREATE INDEX idx_dept_salary ON employees (department, salary DESC); - MySQL 8.0+、PostgreSQL、SQL Server 都能用上这个索引加速窗口函数
- 如果
department值极少(比如只有 3 个部门),而员工百万级,没这个索引可能全表扫描 + 大内存排序,延迟明显
真正容易被忽略的是:即使写了正确的语法,没有对应索引,在大表上执行可能从毫秒级变成秒级甚至超时。别只盯着 SQL 写对了,数据分布和索引才是落地时卡住的关键点。










