一、DENSE_RANK() 是什么?(一句话解释)
"并列排名,不跳号,最紧凑"
就像学校成绩排名:如果两个人并列第一,下一个就是第二,不会跳过。
张三: 100分 → 第 1 名
李四: 100分 → 第 1 名 ← 并列
王五: 95分 → 第 2 名 ← 紧跟着,不跳过
赵六: 90分 → 第 3 名
DENSE = 密集的、紧凑的,所以叫"密集排名"。
二、三大排名函数对比
| 函数 | 相同值处理 | 示例 | 特点 | 适用场景 |
|---|---|---|---|---|
ROW_NUMBER() |
强制不同 | 1, 2, 3, 4 | 即使分数相同,排名也不同 | 去重、分页 |
RANK() |
并列,跳号 | 1, 1, 3, 4 | 有并列时,后面的排名会跳过 | 奥运奖牌榜 |
DENSE_RANK() |
并列,不跳号 | 1, 1, 2, 3 | 紧凑排名,最符合直觉 | 成绩排名、等级划分 |
直观对比:
sql
SELECT
name,
score,
ROW_NUMBER() OVER (ORDER BY score DESC) AS row_num,
RANK() OVER (ORDER BY score DESC) AS rank,
DENSE_RANK() OVER (ORDER BY score DESC) AS dense_rank
FROM students;
-- 结果:
-- name | score | row_num | rank | dense_rank
-- ------+-------+---------+------+------------
-- 张三 | 100 | 1 | 1 | 1
-- 李四 | 100 | 2 | 1 | 1 ← 并列第一
-- 王五 | 95 | 3 | 3 | 2 ← RANK 跳过 2,DENSE 不跳
-- 赵六 | 90 | 4 | 4 | 3
-- 钱七 | 90 | 5 | 4 | 3 ← 又一对并列
-- 孙八 | 85 | 6 | 6 | 4 ← RANK 跳过 5,DENSE 只到 4
三、8 个实用场景
场景 1:学生成绩排名(最经典)
需求: 给全班学生排名,相同分数排名相同
sql
SELECT
student_id,
name,
score,
DENSE_RANK() OVER (ORDER BY score DESC) AS rank
FROM exam_scores
ORDER BY rank;
为什么用 DENSE_RANK?
- 如果用
ROW_NUMBER():两个 100 分的学生,一个第 1,一个第 2,不公平 - 如果用
RANK():两个 100 分后,下一个 95 分变成第 3 名,学生家长会问"第 2 名去哪了?" - 用
DENSE_RANK():100 分都是第 1,95 分是第 2,合情合理
场景 2:等级划分(ABCDE 级)
需求: 根据销售额划分等级:前 10% A 级,10-30% B 级,30-60% C 级...
sql
SELECT
emp_name,
sales_amount,
DENSE_RANK() OVER (ORDER BY sales_amount DESC) AS rank,
COUNT(*) OVER () AS total_count,
CASE
WHEN DENSE_RANK() OVER (ORDER BY sales_amount DESC)::DECIMAL / COUNT(*) OVER () <= 0.1 THEN 'A 级'
WHEN DENSE_RANK() OVER (ORDER BY sales_amount DESC)::DECIMAL / COUNT(*) OVER () <= 0.3 THEN 'B 级'
WHEN DENSE_RANK() OVER (ORDER BY sales_amount DESC)::DECIMAL / COUNT(*) OVER () <= 0.6 THEN 'C 级'
ELSE 'D 级'
END AS grade
FROM sales_performance;
优势: 比固定阈值更灵活,自动适应数据分布
场景 3:找出前 N 个不同值
需求: 找出销售额最高的 5 个不同金额(可能有多个员工对应同一金额)
sql
SELECT DISTINCT sales_amount
FROM (
SELECT
sales_amount,
DENSE_RANK() OVER (ORDER BY sales_amount DESC) AS rank
FROM sales_performance
) t
WHERE rank <= 5;
与 ROW_NUMBER 的区别:
ROW_NUMBER():返回前 5 条记录(可能只有 3 个不同金额)DENSE_RANK():返回前 5 个不同金额(可能有 10 条记录)
场景 4:分组排名(每个部门内部)
需求: 每个部门内按业绩排名
sql
SELECT
dept_name,
emp_name,
sales_amount,
DENSE_RANK() OVER (PARTITION BY dept_name ORDER BY sales_amount DESC) AS dept_rank
FROM employees
ORDER BY dept_name, dept_rank;
结果示例:
dept_name | emp_name | sales_amount | dept_rank
----------+----------+--------------+----------
销售部 | 张三 | 100000 | 1
销售部 | 李四 | 100000 | 1 ← 并列
销售部 | 王五 | 95000 | 2
技术部 | 赵六 | 80000 | 1
技术部 | 钱七 | 75000 | 2
场景 5:计算百分位排名
需求: 计算每个员工的业绩在公司的百分位
sql
SELECT
emp_name,
sales_amount,
DENSE_RANK() OVER (ORDER BY sales_amount DESC) AS rank,
COUNT(DISTINCT sales_amount) OVER () AS unique_count,
ROUND(
(DENSE_RANK() OVER (ORDER BY sales_amount DESC) - 1)::DECIMAL /
NULLIF(COUNT(DISTINCT sales_amount) OVER (), 0) * 100,
2
) AS percentile
FROM sales_performance;
解读:
percentile = 0:最高业绩(第 1 名)percentile = 50:中等水平percentile = 99:接近最低
场景 6:检测数据分布
需求: 分析销售金额的分布情况
sql
SELECT
DENSE_RANK() OVER (ORDER BY sales_amount DESC) AS rank,
COUNT(*) AS emp_count,
AVG(sales_amount) AS avg_sales,
MIN(sales_amount) AS min_sales,
MAX(sales_amount) AS max_sales
FROM (
SELECT
emp_name,
sales_amount,
DENSE_RANK() OVER (ORDER BY sales_amount DESC) AS rank
FROM sales_performance
) t
GROUP BY rank
ORDER BY rank
LIMIT 10; -- 看前 10 个档次的分布
用途: 快速了解业绩集中度,是否有断层
场景 7:去重并保留所有并列记录
需求: 删除重复订单,但金额和时间完全相同的订单都保留
sql
-- 使用 DENSE_RANK 标记
SELECT
id,
order_no,
amount,
created_at,
DENSE_RANK() OVER (
PARTITION BY order_no, amount, created_at
ORDER BY id
) AS rn
FROM orders;
-- 所有 rn=1 的都是唯一或并列的记录
-- rn>1 的是真正的重复(完全相同的字段)
DELETE FROM orders
WHERE id IN (
SELECT id FROM (
SELECT
id,
DENSE_RANK() OVER (
PARTITION BY order_no, amount, created_at
ORDER BY id
) AS rn
FROM orders
) t
WHERE rn > 1
);
场景 8:生成连续序号用于报表
需求: 报表需要连续的序号列,即使数据有并列
sql
SELECT
DENSE_RANK() OVER (ORDER BY category, sub_category) AS item_no,
category,
sub_category,
product_name,
sales_amount
FROM products
ORDER BY item_no;
优势: 序号连续美观,适合打印报表
四、核心语法
sql
DENSE_RANK() OVER (
PARTITION BY column1, column2 -- 可选:分组依据
ORDER BY column3 DESC -- 必填:排序规则
)
关键点:
- 不需要参数 :
DENSE_RANK()括号里是空的 - 必须配合 OVER():声明这是窗口函数
- ORDER BY 必填:决定排名顺序
- PARTITION BY 可选:是否分组排名
五、性能优化
1. 避免多次调用
sql
-- ❌ 慢:多次计算 DENSE_RANK
SELECT
emp_name,
DENSE_RANK() OVER (ORDER BY sales DESC) AS rank,
CASE
WHEN DENSE_RANK() OVER (ORDER BY sales DESC) <= 10 THEN '优秀'
ELSE '普通'
END AS level
FROM employees;
-- ✅ 快:用子查询或 CTE
WITH ranked AS (
SELECT
emp_name,
sales,
DENSE_RANK() OVER (ORDER BY sales DESC) AS rank
FROM employees
)
SELECT
emp_name,
rank,
CASE WHEN rank <= 10 THEN '优秀' ELSE '普通' END AS level
FROM ranked;
2. 合理使用索引
sql
-- 为 ORDER BY 字段创建索引
CREATE INDEX idx_employees_sales ON employees (sales_amount DESC);
-- 为 PARTITION BY + ORDER BY 创建复合索引
CREATE INDEX idx_emp_dept_sales ON employees (dept_id, sales_amount DESC);
3. 减少窗口范围
sql
-- 如果不需要分组,不要加 PARTITION BY
DENSE_RANK() OVER (PARTITION BY 1 ORDER BY id) -- 多余!
-- ✅ 直接全局排名
DENSE_RANK() OVER (ORDER BY id)
六、常见错误
错误 1:在 WHERE 中直接使用
sql
-- 错误
SELECT * FROM employees
WHERE DENSE_RANK() OVER (ORDER BY salary DESC) <= 10;
-- ✅ 正确:用子查询
SELECT * FROM (
SELECT *, DENSE_RANK() OVER (ORDER BY salary DESC) AS rank
FROM employees
) t
WHERE rank <= 10;
错误 2:忘记 ORDER BY
sql
-- ❌ 错误:DENSE_RANK 必须有排序规则
DENSE_RANK() OVER (PARTITION BY dept_id)
-- ✅ 正确
DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC)
错误 3:混淆 RANK 和 DENSE_RANK
sql
-- 场景:找前 10 名员工
-- 用 RANK:如果有并列,可能返回超过 10 人
SELECT * FROM (
SELECT *, RANK() OVER (ORDER BY sales DESC) AS rank
FROM employees
) t WHERE rank <= 10; -- 可能返回 12 人(如果有并列)
-- 用 DENSE_RANK:保证最多 10 个不同档次
SELECT * FROM (
SELECT *, DENSE_RANK() OVER (ORDER BY sales DESC) AS rank
FROM employees
) t WHERE rank <= 10; -- 最多 10 个档次,但人数可能很多
-- 用 ROW_NUMBER:严格 10 人
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (ORDER BY sales DESC) AS rn
FROM employees
) t WHERE rn <= 10; -- 严格 10 人
七、选择指南:什么时候用 DENSE_RANK?
| 场景 | 推荐函数 | 原因 |
|---|---|---|
| 学生成绩排名 | ✅ DENSE_RANK() |
家长能理解"第 2 名" |
| 员工绩效考核 | ✅ DENSE_RANK() |
等级划分清晰 |
| 奥运奖牌榜 | ❌ RANK() |
并列金牌,没有银牌 |
| 分页查询 | ❌ ROW_NUMBER() |
需要精确行数 |
| 找前 N 个不同值 | ✅ DENSE_RANK() |
关注档次而非人数 |
| 生成报表序号 | ✅ DENSE_RANK() |
序号连续美观 |
八、记忆口诀
DENSE_RANK 不跳号,并列之后接着跑
成绩排名最合适,等级划分也巧妙
若要严格控人数,ROW_NUMBER 来效劳
奥运奖牌要跳号,RANK 函数不能少
九、总结
核心要点
- DENSE_RANK() = 并列排名,不跳号(1, 1, 2, 3)
- 适用场景 = 成绩排名、等级划分、前 N 个不同值
- 最大优势 = 排名连续,符合人类直觉
- 与 RANK 区别 = 是否跳号
- 使用时机 = 需要"公平且紧凑"的排名时
快速参考
sql
-- 基本模板
SELECT * FROM (
SELECT
字段列表,
DENSE_RANK() OVER (
PARTITION BY 分组字段 -- 可选
ORDER BY 排序字段 DESC -- 必填
) AS rank
FROM 表名
) t
WHERE rank <= N; -- 前 N 个档次
实战速查
sql
-- 1. 全班排名
SELECT name, score, DENSE_RANK() OVER (ORDER BY score DESC) AS rank
FROM students;
-- 2. 部门内排名
SELECT dept, name, sales,
DENSE_RANK() OVER (PARTITION BY dept ORDER BY sales DESC) AS dept_rank
FROM employees;
-- 3. 前 10% 等级
SELECT name, sales,
CASE WHEN DENSE_RANK() OVER (ORDER BY sales DESC)::DECIMAL /
COUNT(*) OVER () <= 0.1 THEN 'A' ELSE 'B' END AS grade
FROM employees;
-- 4. 找前 5 个不同金额
SELECT DISTINCT amount FROM (
SELECT amount, DENSE_RANK() OVER (ORDER BY amount DESC) AS rn
FROM orders
) t WHERE rn <= 5;