1. 为什么需要分组排序函数在日常数据处理中我们经常遇到这样的需求既要按照某个字段分组又要在组内按照另一个字段排序。比如统计每个班级的学生成绩排名、计算各部门员工的薪资排名、分析各品类商品的销量排行等。这类需求如果只用基础的GROUP BY和ORDER BY组合往往难以完美实现。我刚开始接触Oracle时就踩过这个坑。当时需要统计每个部门的业绩前三名员工结果用GROUP BYORDER BY折腾了半天发现根本达不到预期效果。后来才知道Oracle提供了rank()和row_number()这类高级分析函数专门解决这类分组排序问题。这两个函数的核心优势在于分组和排序一体化不需要先GROUP BY再ORDER BY的繁琐操作灵活处理并列排名rank()会自动处理相同值的排名问题高效执行相比子查询等复杂写法性能更好2. row_number()函数详解2.1 基本语法与原理row_number()函数的完整语法是这样的row_number() over(partition by 分组列 order by 排序列 [asc|desc])这个函数的工作原理很有意思 - 它会在执行完WHERE、GROUP BY这些基础操作后再对结果集进行分组编号。也就是说over()里面的操作是在查询的最后阶段执行的。举个实际例子假设我们有学生成绩表-- 创建表 CREATE TABLE student_scores ( id INT PRIMARY KEY, name VARCHAR(50), class VARCHAR(10), score INT ); -- 插入测试数据 INSERT INTO student_scores VALUES (1, Alice, A, 85); INSERT INTO student_scores VALUES (2, Bob, A, 90); INSERT INTO student_scores VALUES (3, Charlie, B, 95); INSERT INTO student_scores VALUES (4, David, B, 80); INSERT INTO student_scores VALUES (5, Emma, A, 75); INSERT INTO student_scores VALUES (6, Frank, B, 85);2.2 三种典型用法第一种单纯排序不分组select id,name,class,score, row_number() over(order by score desc) ranking from student_scores这个查询会给所有学生按成绩降序编号不考虑班级分组。适合全校排名场景。第二种先分组再排序select id,name,class,score, row_number() over(partition by class order by score desc) ranking from student_scores这才是真正的分组排序 - 先按class分组再在每个班级内按成绩排序编号。第三种取每组前N名select * from ( select id,name,class,score, row_number() over(partition by class order by score desc) ranking from student_scores ) where ranking 2通过子查询包装我们可以轻松取出每个班级的第一名。把2改成3就是取前三名非常实用。3. rank()函数的特殊之处3.1 rank() vs row_number()很多初学者分不清rank()和row_number()的区别。其实关键就在于如何处理相同值row_number()即使值相同也会分配连续的不同序号1,2,3,4...rank()相同值会得到相同序号下一个不同值会跳过相应数量的序号1,2,2,4...来看具体例子-- 使用rank() SELECT id, name, class, score, RANK() OVER(PARTITION BY class ORDER BY score DESC) AS ranking FROM student_scores; -- 使用row_number() SELECT id, name, class, score, row_number() OVER(PARTITION BY class ORDER BY score DESC) AS ranking FROM student_scores;在A班Bob 90分第一Alice 85分第二Emma 75分第三。但如果A班有两个90分rank()会并列第一下一个是第三名而row_number()仍然是第一、第二。3.2 实际业务场景选择选择哪个函数取决于业务需求需要严格区分名次比如抽奖、考试录取用row_number()允许并列排名比如体育比赛、销售排名用rank()需要密集排名无间隔可以用dense_rank()我曾经做过一个销售奖励系统就因为选错函数闹过笑话。用row_number()导致两个业绩相同的销售员一个拿一等奖一个拿二等奖后来改用rank()才解决问题。4. 高级应用技巧4.1 多列分组排序partition by支持多个字段实现更复杂的分组逻辑-- 按班级和性别双重分组后排序 select id,name,class,gender,score, row_number() over(partition by class,gender order by score desc) ranking from student_scores4.2 结合其他分析函数这些排序函数可以和其他分析函数组合使用-- 计算每个班级的平均分并标注高于平均分的学生 select id,name,class,score, avg(score) over(partition by class) as avg_score, case when score avg(score) over(partition by class) then Y else N end as above_avg from student_scores4.3 性能优化建议虽然这些函数很强大但在大数据量下要注意确保order by的列有索引避免过度分区partition by的列不宜过多考虑先用where条件过滤数据减少处理量我在处理一个百万级数据表时就遇到过性能问题。后来在排序列上加了索引查询时间从15秒降到了0.5秒。5. 常见问题排查5.1 为什么结果不符合预期新手常遇到的几个坑忘记写order by导致随机排序排序列有null值导致结果异常混淆partition by和group by的用法5.2 窗口函数执行顺序要特别注意这些函数的执行顺序先执行FROM和WHERE子句然后执行GROUP BY接着执行HAVING最后才执行窗口函数over里面的内容5.3 与其他SQL语法的配合窗口函数可以和其他SQL语法自由组合但要注意不能直接在WHERE中使用窗口函数结果可以放在SELECT子句或ORDER BY中需要通过子查询或CTE来过滤窗口函数结果6. 真实案例销售数据分析去年我帮一个电商客户做过销售分析系统核心需求是按商品品类分组统计每个品类下商品的销量排名标记出每个品类的前三名最终解决方案是这样的WITH sales_ranking AS ( SELECT product_id, category, sales, RANK() OVER(PARTITION BY category ORDER BY sales DESC) as rank FROM product_sales WHERE sales_date BETWEEN 2023-01-01 AND 2023-12-31 ) SELECT product_id, category, sales, CASE WHEN rank 3 THEN TOP3 ELSE OTHER END as tag FROM sales_ranking这个查询不仅解决了分组排序问题还用CASE语句给TOP3商品打了标签后续做可视化分析非常方便。