优先用rank() over (partition by dept_id order by salary desc),因它支持并列第一,符合“最高薪资员工”业务语义;row_number()强制唯一编号会漏掉真实并列者,dense_rank()在此场景无优势。

用 RANK() 还是 ROW_NUMBER()?关键看是否允许并列
要找部门最高薪资员工,最直接的方式是用窗口函数给每组内薪资排序,但选哪个排序函数决定结果是否包含并列第一。比如两个员工同为部门最高薪,RANK() 会都标为 1,而 ROW_NUMBER() 强制分配唯一序号(如 1 和 2),导致后者漏掉真实并列者。
- 优先用
RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC)—— 更符合“最高薪资员工”的业务语义 - 如果明确要求只取一个(哪怕有并列),再考虑
ROW_NUMBER() -
DENSE_RANK()在这里没优势:它用于连续排名场景(如前三名),不是“最高”这种单点需求
WHERE 子句里不能直接写窗口函数,得套一层子查询或 CTE
常见错误是写成 SELECT * FROM emp WHERE RANK() OVER (...) = 1,这会报错:window function is not allowed here。SQL 标准规定窗口函数只能出现在 SELECT 或 ORDER BY 子句中,不能用于 WHERE 过滤。
- 正确做法:先在子查询或 CTE 中计算排名,再外层筛选
rn = 1 - 例如:
WITH ranked AS ( SELECT *, RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn FROM emp ) SELECT emp_id, name, dept_id, salary FROM ranked WHERE rn = 1;
- 别用
SELECT *做子查询——尤其当表有大字段(如TEXT、JSON)时,会拖慢整个过程
性能隐患:没加索引时,PARTITION BY dept_id ORDER BY salary DESC 可能全表扫描
窗口函数本身不自动利用索引,执行计划里若看到 WindowAgg 下挂的是 Seq Scan,说明数据库没走索引加速分组和排序。
- 必须建复合索引:
CREATE INDEX idx_dept_salary ON emp(dept_id, salary DESC); - 注意顺序:
dept_id在前(对应PARTITION BY),salary DESC在后(匹配ORDER BY方向) - PostgreSQL 14+ 和 MySQL 8.0.29+ 支持该索引优化窗口函数;SQLite 目前不支持,得靠物化临时表
NULL 薪资值会让 RANK() 把它排在最前面,需要提前过滤
默认情况下,ORDER BY salary DESC 会把 NULL 当作最大值(SQL 标准行为),导致 RANK() 给 NULL 分配 1 —— 明显不符合“最高薪资”本意。
- 加
WHERE salary IS NOT NULL最简单可靠 - 或者改用
ORDER BY salary DESC NULLS LAST(PostgreSQL/Oracle 支持;MySQL 不支持该语法) - 别依赖
COALESCE(salary, 0)替代:万一真有零薪合法员工,逻辑就错了
窗口函数本身不难,但实际跑起来卡顿或结果不对,往往出在索引缺失、NULL 处理或嵌套层级写错这三处。动手前先 EXPLAIN 看执行计划,比调参数更管用。











