PostgreSQL RANK() 窗口函数实例详解
在PostgreSQL中,RANK() 是一个窗口函数,用于在结果集中为每一行分配一个排名。它与 ROW_NUMBER() 类似,但在处理具有相同值的行时有所不同。RANK() 会为所有具有相同值的行分配相同的排名,并跳过随后的排名(例如,如果有两个第一名,下一个排名将是第三名,而不是第二名)。
基本用法
RANK() 函数的基本语法如下:
RANK() OVER (ORDER BY column [ASC|DESC] [NULLS {FIRST|LAST}])一、RANK() 是什么?(一句话解释)
“并列排名,但会跳号”
就像奥运会奖牌榜:如果两个人并列第一,就没有第二名,直接跳到第三名。
张三: 100分 → 第 1 名 李四: 100分 → 第 1 名 ← 并列 王五: 95分 → 第 3 名 ← 跳过第 2 名! 赵六: 90分 → 第 4 名
二、和 ROW_NUMBER() 的区别
| 函数 | 相同值处理 | 示例 | 特点 |
|---|---|---|---|
ROW_NUMBER() | 强制不同 | 1, 2, 3, 4 | 即使分数相同,排名也不同 |
RANK() | 并列,跳号 | 1, 1, 3, 4 | 有并列时,后面的排名会跳过 |
DENSE_RANK() | 并列,不跳号 | 1, 1, 2, 3 | 紧凑排名,最符合直觉 |
直观对比:
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
-- 赵六 | 90 | 4 | 4 | 3三、8 个实用场景
场景 1:生成排行榜(允许并列)
需求: 销售排行榜,业绩相同的销售人员排名相同
SELECT
emp_name,
sales_amount,
RANK() OVER (ORDER BY sales_amount DESC) AS rank
FROM sales_performance
WHERE rank <= 10; -- 前 10 名(可能超过 10 人,因为有并列)为什么用 RANK 不用 ROW_NUMBER?
- 如果用
ROW_NUMBER():两个业绩相同的人,一个排第 1,一个排第 2,不公平 - 用
RANK():两人都是第 1 名,公平合理
场景 2:找出所有并列第一名
需求: 找出考试成绩最高的所有学生(可能有多个)
SELECT * FROM (
SELECT
student_id,
name,
score,
RANK() OVER (ORDER BY score DESC) AS rank
FROM exam_scores
) t
WHERE rank = 1; -- 所有并列第一的学生优势: 比 MAX() + 子查询更简洁
场景 3:分段奖励(金银铜牌)
需求: 给前 3 名发金牌,4-10 名发银牌,11-50 名发铜牌
SELECT
emp_name,
sales_amount,
RANK() OVER (ORDER BY sales_amount DESC) AS rank,
CASE
WHEN RANK() OVER (ORDER BY sales_amount DESC) <= 3 THEN '🥇 金牌'
WHEN RANK() OVER (ORDER BY sales_amount DESC) <= 10 THEN '🥈 银牌'
WHEN RANK() OVER (ORDER BY sales_amount DESC) <= 50 THEN ' 铜牌'
ELSE '参与奖'
END AS award
FROM sales_performance;
注意: 如果有并列,可能导致某个奖项人数超预期(比如 5 个人并列第 3,那金牌就有 5 个)。
场景 4:检测数据异常(跳号分析)
需求: 找出订单编号不连续的地方
SELECT
order_no,
created_at,
RANK() OVER (ORDER BY order_no) AS expected_no,
order_no - RANK() OVER (ORDER BY order_no) AS gap
FROM orders
WHERE order_no - RANK() OVER (ORDER BY order_no) > 0; -- 有缺口的地方
原理: 如果编号连续,order_no - RANK() 应该始终是同一个值;如果出现变化,说明中间有缺失。
场景 5:分组后取每组的前 N 名(含并列)
需求: 每个部门业绩最好的员工(包括并列)
SELECT * FROM (
SELECT
dept_name,
emp_name,
sales_amount,
RANK() OVER (PARTITION BY dept_name ORDER BY sales_amount DESC) AS rank
FROM employees
) t
WHERE rank <= 3; -- 每个部门前 3 名(含并列)
与 ROW_NUMBER 的区别:
ROW_NUMBER():每个部门严格 3 个人RANK():如果第 3 名有并列,可能返回 4、5 个人
场景 6:计算累计占比
需求: 计算每个销售额在总销售额中的累计占比
SELECT
emp_name,
sales_amount,
SUM(sales_amount) OVER (ORDER BY sales_amount DESC) AS running_total,
SUM(sales_amount) OVER () AS grand_total,
ROUND(
SUM(sales_amount) OVER (ORDER BY sales_amount DESC)::DECIMAL /
SUM(sales_amount) OVER () * 100,
2
) AS cumulative_percent
FROM sales_performance;
结合 RANK 使用:
-- 查看前 20% 的销售人员贡献了多少业绩
SELECT * FROM (
SELECT
emp_name,
sales_amount,
RANK() OVER (ORDER BY sales_amount DESC) AS rank,
COUNT(*) OVER () AS total_count
FROM sales_performance
) t
WHERE rank::DECIMAL / total_count <= 0.2; -- 前 20%
场景 7:去重但保留并列记录
需求: 删除重复订单,但如果金额和时间完全相同,保留所有
-- ❌ ROW_NUMBER 会只保留一条
DELETE FROM orders
WHERE id IN (
SELECT id FROM (
SELECT
id,
order_no,
ROW_NUMBER() OVER (PARTITION BY order_no ORDER BY id) AS rn
FROM orders
) t
WHERE rn > 1
);
-- ✅ RANK 会保留所有并列的
DELETE FROM orders
WHERE id IN (
SELECT id FROM (
SELECT
id,
order_no,
RANK() OVER (PARTITION BY order_no, amount, created_at ORDER BY id) AS rn
FROM orders
) t
WHERE rn > 1
);场景 8:生成序号但允许空缺
需求: 给学生分配座位号,但如果有请假,座位号跳过
SELECT
student_id,
name,
status,
CASE
WHEN status = '请假' THEN NULL
ELSE RANK() OVER (
PARTITION BY CASE WHEN status != '请假' THEN 1 ELSE 0 END
ORDER BY student_id
)
END AS seat_number
FROM students
ORDER BY student_id;
四、核心语法
RANK() OVER (
PARTITION BY column1, column2 -- 可选:分组依据
ORDER BY column3 DESC -- 必填:排序规则
)
关键点:
- 不需要参数:
RANK()括号里是空的 - 必须配合 OVER():声明这是窗口函数
- ORDER BY 必填:决定排名顺序
- PARTITION BY 可选:是否分组排名
五、性能优化
1. 避免重复计算
-- ❌ 慢:多次调用 RANK()
SELECT
emp_name,
RANK() OVER (ORDER BY sales DESC) AS rank,
CASE
WHEN RANK() OVER (ORDER BY sales DESC) <= 10 THEN '优秀'
ELSE '普通'
END AS level
FROM employees;
-- ✅ 快:用子查询或 CTE
WITH ranked AS (
SELECT
emp_name,
sales,
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. 合理使用索引
-- 为 ORDER BY 字段创建索引
CREATE INDEX idx_sales_perf_sales ON sales_performance (sales_amount DESC);
-- 这样 RANK() 计算会更快
SELECT
emp_name,
sales_amount,
RANK() OVER (ORDER BY sales_amount DESC) AS rank
FROM sales_performance;六、常见错误
错误 1:在 WHERE 中直接使用
-- ❌ 错误
SELECT * FROM employees
WHERE RANK() OVER (ORDER BY salary DESC) <= 10;
-- ✅ 正确:用子查询
SELECT * FROM (
SELECT *, RANK() OVER (ORDER BY salary DESC) AS rank
FROM employees
) t
WHERE rank <= 10;错误 2:忘记 ORDER BY
-- ❌ 错误:RANK 必须有排序规则 RANK() OVER (PARTITION BY dept_id) -- ✅ 正确 RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC)
错误 3:误解跳号逻辑
-- 假设有 3 个人并列第 1
SELECT
name,
score,
RANK() OVER (ORDER BY score DESC) AS rank
FROM students;
-- 结果:
-- name | score | rank
-- ------+-------+------
-- 张三 | 100 | 1
-- 李四 | 100 | 1
-- 王五 | 100 | 1
-- 赵六 | 95 | 4 ← 不是 2!跳过了 2 和 3七、RANK vs DENSE_RANK 选择指南
| 场景 | 推荐函数 | 原因 |
|---|---|---|
| 奥运会奖牌榜 | RANK() | 并列金牌,没有银牌 |
| 学生成绩排名 | DENSE_RANK() | 并列第一,下一个是第二 |
| 分页查询 | ROW_NUMBER() | 需要精确控制行数 |
| 前 N 名(含并列) | RANK() | 确保不遗漏并列者 |
| 紧凑排名 | DENSE_RANK() | 排名连续,无空缺 |
八、记忆口诀
RANK 排名会跳号,并列之后空位跑 奥运奖牌最典型,金银铜牌不颠倒 若要排名更紧凑,DENSE_RANK 来效劳 分页去重用 ROW,场景不同选对宝
九、总结
核心要点
- RANK() = 并列排名,但会跳号(1, 1, 3, 4)
- 适用场景 = 排行榜、找并列第一、分段奖励
- 与 ROW_NUMBER 区别 = 相同值处理方式不同
- 与 DENSE_RANK 区别 = 是否跳号
- 使用时机 = 需要"公平排名"且允许空缺时
快速参考
-- 基本模板
SELECT * FROM (
SELECT
字段列表,
RANK() OVER (
PARTITION BY 分组字段 -- 可选
ORDER BY 排序字段 DESC -- 必填
) AS rank
FROM 表名
) t
WHERE rank <= N; -- 前 N 名(含并列)到此这篇关于PostgreSQL RANK() 窗口函数实例详解的文章就介绍到这了,更多相关PostgreSQL RANK() 窗口函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
PostgreSQL 查看数据库,索引,表,表空间大小的示例代码
PostgreSQL 提供了多个系统管理函数来查看表,索引,表空间及数据库的大小,下面详细介绍一下2013-08-08


最新评论