DENSE_RANK()适用于同薪同名且不跳号的排名需求,而RANK()并列后跳号、ROW_NUMBER()强制唯一;需按category分组、salary降序排序,并用子查询过滤结果。

为什么用 DENSE_RANK() 而不是 RANK() 或 ROW_NUMBER()
当同一类别的员工薪资相同时,你希望并列名次(比如两个 15000 元都算第 1 名),且后续名次不跳过(即下一个更低薪资是第 2 名,不是第 3 名)——这正是 DENSE_RANK() 的行为。而 RANK() 并列后会跳号(两个第 1 名后直接第 3 名),ROW_NUMBER() 则强制唯一序号,完全无视并列。
实操建议:
- 按
category分组、按salary降序排序是必须的,否则排名无意义 - 若业务要求“同薪同名、不跳号”,
DENSE_RANK()是唯一合理选择 - 注意 NULL 值:
salary为 NULL 时,DENSE_RANK()默认排在最前(因 ORDER BY 中 NULLS FIRST 是多数数据库默认),需显式加NULLS LAST避免误入前三
SQL 写法:子查询 + 过滤是最稳妥的写法
窗口函数不能直接在 WHERE 中使用,所以必须嵌套一层子查询或 CTE。直接写 WHERE DENSE_RANK() OVER (...) 会报错:<code>window function is not allowed in WHERE clause。
示例(标准写法):
SELECT category, name, salary
FROM (
SELECT
category,
name,
salary,
DENSE_RANK() OVER (
PARTITION BY category
ORDER BY salary DESC NULLS LAST
) AS drk
FROM employees
) ranked
WHERE drk <= 3;关键点:
-
PARTITION BY category确保每个类别独立排名 -
ORDER BY salary DESC NULLS LAST防止 NULL 挤占前三位置 - 别名
drk必须在子查询中定义,外层才能过滤
MySQL 8.0+、PostgreSQL、SQL Server 都支持,但 SQLite 不行
SQLite 直到 3.25.0(2018 年)才支持窗口函数,且部分发行版仍默认禁用。执行前务必确认:
SELECT sqlite_version(); -- 应 ≥ 3.25.0<br>SELECT * FROM pragma_compile_options WHERE compile_options LIKE '%WINDOW%';
如果返回空,说明当前 SQLite 编译未启用窗口函数支持,DENSE_RANK() 会报错:no such function: DENSE_RANK。此时只能退回到自连接或变量模拟(复杂且低效),不建议用于生产。
其他数据库兼容性:
- PostgreSQL:原生支持,语法一致
- MySQL 8.0+:支持,但注意
NULLS LAST在 MySQL 中无效(它固定把 NULL 排最后),可省略 - SQL Server:支持,同样忽略
NULLS LAST,但可用ORDER BY ISNULL(salary, 0) DESC替代控制逻辑
性能隐患:没加索引时,PARTITION BY + ORDER BY 可能全表扫描
DENSE_RANK() 在大数据量下性能取决于底层排序效率。如果没有针对 (category, salary) 的复合索引,数据库大概率对每条记录重新排序,IO 和 CPU 开销陡增。
优化建议:
- 创建联合索引:
CREATE INDEX idx_cat_salary ON employees (category, salary DESC); - 避免在
SELECT中对salary做计算(如salary * 1.1),否则索引失效 - 如果只查特定几个
category,先用 WHERE 过滤再开窗口,比全量排名后过滤更高效
真正容易被忽略的是:即使写了 PARTITION BY category,数据库也不会自动利用单列 category 索引加速排序——必须是 (category, salary) 这种前导列匹配的复合索引才有效。

















