PostgreSQL DENSE_RANK() 窗口函数完全解析

📅 2026/6/25 14:31:27
PostgreSQL DENSE_RANK() 窗口函数完全解析
一、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紧凑排名最符合直觉成绩排名、等级划分直观对比SELECTname,score,ROW_NUMBER()OVER(ORDERBYscoreDESC)ASrow_num,RANK()OVER(ORDERBYscoreDESC)ASrank,DENSE_RANK()OVER(ORDERBYscoreDESC)ASdense_rankFROMstudents;-- 结果:-- name | score | row_num | rank | dense_rank-- ------------------------------------------ 张三 | 100 | 1 | 1 | 1-- 李四 | 100 | 2 | 1 | 1 ← 并列第一-- 王五 | 95 | 3 | 3 | 2 ← RANK 跳过 2DENSE 不跳-- 赵六 | 90 | 4 | 4 | 3-- 钱七 | 90 | 5 | 4 | 3 ← 又一对并列-- 孙八 | 85 | 6 | 6 | 4 ← RANK 跳过 5DENSE 只到 4三、8 个实用场景场景 1学生成绩排名最经典需求给全班学生排名相同分数排名相同SELECTstudent_id,name,score,DENSE_RANK()OVER(ORDERBYscoreDESC)ASrankFROMexam_scoresORDERBYrank;为什么用 DENSE_RANK如果用ROW_NUMBER()两个 100 分的学生一个第 1一个第 2不公平如果用RANK()两个 100 分后下一个 95 分变成第 3 名学生家长会问第 2 名去哪了用DENSE_RANK()100 分都是第 195 分是第 2合情合理场景 2等级划分ABCDE 级需求根据销售额划分等级前 10% A 级10-30% B 级30-60% C 级…SELECTemp_name,sales_amount,DENSE_RANK()OVER(ORDERBYsales_amountDESC)ASrank,COUNT(*)OVER()AStotal_count,CASEWHENDENSE_RANK()OVER(ORDERBYsales_amountDESC)::DECIMAL/COUNT(*)OVER()0.1THENA 级WHENDENSE_RANK()OVER(ORDERBYsales_amountDESC)::DECIMAL/COUNT(*)OVER()0.3THENB 级WHENDENSE_RANK()OVER(ORDERBYsales_amountDESC)::DECIMAL/COUNT(*)OVER()0.6THENC 级ELSED 级ENDASgradeFROMsales_performance;优势比固定阈值更灵活自动适应数据分布场景 3找出前 N 个不同值需求找出销售额最高的 5 个不同金额可能有多个员工对应同一金额SELECTDISTINCTsales_amountFROM(SELECTsales_amount,DENSE_RANK()OVER(ORDERBYsales_amountDESC)ASrankFROMsales_performance)tWHERErank5;与 ROW_NUMBER 的区别ROW_NUMBER()返回前 5 条记录可能只有 3 个不同金额DENSE_RANK()返回前 5 个不同金额可能有 10 条记录场景 4分组排名每个部门内部需求每个部门内按业绩排名SELECTdept_name,emp_name,sales_amount,DENSE_RANK()OVER(PARTITIONBYdept_nameORDERBYsales_amountDESC)ASdept_rankFROMemployeesORDERBYdept_name,dept_rank;结果示例dept_name | emp_name | sales_amount | dept_rank -------------------------------------------- 销售部 | 张三 | 100000 | 1 销售部 | 李四 | 100000 | 1 ← 并列 销售部 | 王五 | 95000 | 2 技术部 | 赵六 | 80000 | 1 技术部 | 钱七 | 75000 | 2场景 5计算百分位排名需求计算每个员工的业绩在公司的百分位SELECTemp_name,sales_amount,DENSE_RANK()OVER(ORDERBYsales_amountDESC)ASrank,COUNT(DISTINCTsales_amount)OVER()ASunique_count,ROUND((DENSE_RANK()OVER(ORDERBYsales_amountDESC)-1)::DECIMAL/NULLIF(COUNT(DISTINCTsales_amount)OVER(),0)*100,2)ASpercentileFROMsales_performance;解读percentile 0最高业绩第 1 名percentile 50中等水平percentile 99接近最低场景 6检测数据分布需求分析销售金额的分布情况SELECTDENSE_RANK()OVER(ORDERBYsales_amountDESC)ASrank,COUNT(*)ASemp_count,AVG(sales_amount)ASavg_sales,MIN(sales_amount)ASmin_sales,MAX(sales_amount)ASmax_salesFROM(SELECTemp_name,sales_amount,DENSE_RANK()OVER(ORDERBYsales_amountDESC)ASrankFROMsales_performance)tGROUPBYrankORDERBYrankLIMIT10;-- 看前 10 个档次的分布用途快速了解业绩集中度是否有断层场景 7去重并保留所有并列记录需求删除重复订单但金额和时间完全相同的订单都保留-- 使用 DENSE_RANK 标记SELECTid,order_no,amount,created_at,DENSE_RANK()OVER(PARTITIONBYorder_no,amount,created_atORDERBYid)ASrnFROMorders;-- 所有 rn1 的都是唯一或并列的记录-- rn1 的是真正的重复完全相同的字段DELETEFROMordersWHEREidIN(SELECTidFROM(SELECTid,DENSE_RANK()OVER(PARTITIONBYorder_no,amount,created_atORDERBYid)ASrnFROMorders)tWHERErn1);场景 8生成连续序号用于报表需求报表需要连续的序号列即使数据有并列SELECTDENSE_RANK()OVER(ORDERBYcategory,sub_category)ASitem_no,category,sub_category,product_name,sales_amountFROMproductsORDERBYitem_no;优势序号连续美观适合打印报表四、核心语法DENSE_RANK()OVER(PARTITIONBYcolumn1,column2-- 可选分组依据ORDERBYcolumn3DESC-- 必填排序规则)关键点不需要参数DENSE_RANK()括号里是空的必须配合 OVER()声明这是窗口函数ORDER BY 必填决定排名顺序PARTITION BY 可选是否分组排名五、性能优化1. 避免多次调用-- ❌ 慢多次计算 DENSE_RANKSELECTemp_name,DENSE_RANK()OVER(ORDERBYsalesDESC)ASrank,CASEWHENDENSE_RANK()OVER(ORDERBYsalesDESC)10THEN优秀ELSE普通ENDASlevelFROMemployees;-- ✅ 快用子查询或 CTEWITHrankedAS(SELECTemp_name,sales,DENSE_RANK()OVER(ORDERBYsalesDESC)ASrankFROMemployees)SELECTemp_name,rank,CASEWHENrank10THEN优秀ELSE普通ENDASlevelFROMranked;2. 合理使用索引-- 为 ORDER BY 字段创建索引CREATEINDEXidx_employees_salesONemployees(sales_amountDESC);-- 为 PARTITION BY ORDER BY 创建复合索引CREATEINDEXidx_emp_dept_salesONemployees(dept_id,sales_amountDESC);3. 减少窗口范围-- 如果不需要分组不要加 PARTITION BYDENSE_RANK()OVER(PARTITIONBY1ORDERBYid)-- 多余-- ✅ 直接全局排名DENSE_RANK()OVER(ORDERBYid)六、常见错误错误 1在 WHERE 中直接使用-- 错误SELECT*FROMemployeesWHEREDENSE_RANK()OVER(ORDERBYsalaryDESC)10;-- ✅ 正确用子查询SELECT*FROM(SELECT*,DENSE_RANK()OVER(ORDERBYsalaryDESC)ASrankFROMemployees)tWHERErank10;错误 2忘记 ORDER BY-- ❌ 错误DENSE_RANK 必须有排序规则DENSE_RANK()OVER(PARTITIONBYdept_id)-- ✅ 正确DENSE_RANK()OVER(PARTITIONBYdept_idORDERBYsalaryDESC)错误 3混淆 RANK 和 DENSE_RANK-- 场景找前 10 名员工-- 用 RANK如果有并列可能返回超过 10 人SELECT*FROM(SELECT*,RANK()OVER(ORDERBYsalesDESC)ASrankFROMemployees)tWHERErank10;-- 可能返回 12 人如果有并列-- 用 DENSE_RANK保证最多 10 个不同档次SELECT*FROM(SELECT*,DENSE_RANK()OVER(ORDERBYsalesDESC)ASrankFROMemployees)tWHERErank10;-- 最多 10 个档次但人数可能很多-- 用 ROW_NUMBER严格 10 人SELECT*FROM(SELECT*,ROW_NUMBER()OVER(ORDERBYsalesDESC)ASrnFROMemployees)tWHERErn10;-- 严格 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 区别 是否跳号使用时机 需要公平且紧凑的排名时快速参考-- 基本模板SELECT*FROM(SELECT字段列表,DENSE_RANK()OVER(PARTITIONBY分组字段-- 可选ORDERBY排序字段DESC-- 必填)ASrankFROM表名)tWHERErankN;-- 前 N 个档次实战速查-- 1. 全班排名SELECTname,score,DENSE_RANK()OVER(ORDERBYscoreDESC)ASrankFROMstudents;-- 2. 部门内排名SELECTdept,name,sales,DENSE_RANK()OVER(PARTITIONBYdeptORDERBYsalesDESC)ASdept_rankFROMemployees;-- 3. 前 10% 等级SELECTname,sales,CASEWHENDENSE_RANK()OVER(ORDERBYsalesDESC)::DECIMAL/COUNT(*)OVER()0.1THENAELSEBENDASgradeFROMemployees;-- 4. 找前 5 个不同金额SELECTDISTINCTamountFROM(SELECTamount,DENSE_RANK()OVER(ORDERBYamountDESC)ASrnFROMorders)tWHERErn5;