导读:本期聚焦于清原小日向创作的《DB2窗口函数ROW_NUMBER与RANK怎么用?去重排名实战案例详解》,敬请观看详情。数据库里要给记录排名或者去重,很多人第一反应是写复杂子查询,其实DB2提供了窗口函数能一步搞定。ROW_NUMBER可以为结果集中的每一行生成连续编号,配合PARTITION BY能轻松实现分组去重,只保留每组最新一条数据。RANK和DENSE_RANK则负责排名场景,相同数值会得到相同名次,区别在于RANK跳跃计数而DENSE_RANK连续计数。本文通过员工绩效排名、分组取最新记录、销售数据并列名次等实际案例,详细讲解这几个函数的语法结构、执行逻辑和常见坑点,比如ORDER BY缺失导致的编号混乱、分区字段选错引发的排名错误等问题,并给出可以直接运行的SQL示例,帮助你在DB2环境中熟练运用窗口函数解决业务问题。

窗口函数是SQL标准中非常重要的一类函数,DB2从9.5版本开始就已经全面支持。与普通聚合函数不同,窗口函数不会把多行合并成一行,而是在保留每一行明细数据的基础上,为每行计算出一个额外的结果值。这个特性使得它在排名、去重、累计求和等场景中几乎是不可替代的方案。本文围绕ROW_NUMBER和RANK这两个最常用的窗口函数,结合几个典型的业务案例,讲解它们的具体用法和容易踩的坑。

DB2窗口函数ROW_NUMBER与RANK怎么用?去重排名实战案例详解

窗口函数的基本语法与执行逻辑

在DB2中,窗口函数的标准写法是在函数名后面跟上OVER子句,OVER子句内部定义分区和排序规则。基本结构如下:

SELECT
    emp_name,
    dept_id,
    salary,
    ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn
FROM employee;

这段SQL的含义是:先按照dept_id将数据分成若干个分区,每个分区内再按salary从高到低排序,然后为分区内的每一行分配一个从1开始的连续编号。PARTITION BY是可选的,如果省略,整个结果集会被当作一个大的分区来处理。ORDER BY在ROW_NUMBER中是必须的,因为数据库必须知道按什么顺序编号,否则生成的编号就没有确定的含义。

需要特别理解的一点是,窗口函数的执行时机在WHERE、GROUP BY、HAVING之后,在ORDER BY和FETCH FIRST之前。这意味着你不能直接在WHERE子句中引用窗口函数的结果,比如写WHERE ROW_NUMBER() OVER(...) = 1在DB2中会报错。正确做法是先用子查询或者CTE生成编号,再在外层进行过滤。这个执行顺序是很多初学者犯错的高频区域,后面案例中会反复体现。

案例一:用ROW_NUMBER实现分组去重

去重是窗口函数最常见的应用场景之一。假设有一张订单变更记录表order_log,同一个订单号可能存在多条变更记录,业务要求只取每个订单最新的一条。传统做法是用关联子查询判断时间戳最大值,数据量大时性能很差。用ROW_NUMBER改写如下:

WITH ranked AS (
    SELECT
        order_id,
        status,
        update_time,
        ROW_NUMBER() OVER (
            PARTITION BY order_id
            ORDER BY update_time DESC
        ) AS rn
    FROM order_log
)
SELECT order_id, status, update_time
FROM ranked
WHERE rn = 1;

这里的思路很清晰:按订单号分区,按更新时间倒序排列,编号为1的就是最新记录。相比关联子查询,这种写法只需要对表扫描一次,DB2优化器能够对窗口函数做高效的排序处理,在百万级数据量下性能差距非常明显。

有一个细节值得注意:如果同一订单存在两条update_time完全相同的记录,ROW_NUMBER会随机给其中一条编号1,结果不稳定。如果业务上需要确定性的结果,应该在ORDER BY中补充一个唯一的辅助字段,比如主键id,写成ORDER BY update_time DESC, id DESC,这样每次执行的结果都一致。这类偶发的结果不一致问题在生产环境排查起来非常困难,建议一开始就养成补齐排序字段的习惯。

另外提醒一点,如果你的目的只是单纯的整行去重(所有列都相同),用DISTINCT或者GROUP BY就够了,不需要动用窗口函数。ROW_NUMBER的价值在于按照特定业务规则保留某一条,两者不要混淆。

案例二:RANK与DENSE_RANK处理并列排名

ROW_NUMBER生成的编号永远是1、2、3、4这样连续的,即使两行的排序值相同,也会被强制分配不同的编号。但在很多排名场景中,相同分数应该得到相同名次,这时就需要RANK登场。先看三者的区别,用一张成绩表来演示:

SELECT
    student_name,
    score,
    ROW_NUMBER() OVER (ORDER BY score DESC) AS row_num,
    RANK()       OVER (ORDER BY score DESC) AS rnk,
    DENSE_RANK() OVER (ORDER BY score DESC) AS dense_rnk
FROM student_score;

假设有三个学生都是95分,两个学生是90分,那么三列的结果分别是:ROW_NUMBER给出1、2、3、4、5这种连续不重复的编号;RANK给三个95分都编为1,后面两个90分则直接跳到4,因为前面实际占了三个位置;DENSE_RANK同样给三个95分编为1,但两个90分是2,名次是连续压缩的。简单总结就是:RANK跳跃、DENSE_RANK连续、ROW_NUMBER不并列。

在实际业务中,这三者的选择取决于需求。体育比赛排名通常用RANK,比如两人并列冠军,下一名就是第三名;计算薪酬分档或者绩效等级时往往用DENSE_RANK,因为档位数量本身是有限的;而纯粹为了去重或者取前N条,ROW_NUMBER就够了。

再补充一个组合用法:如果要取每个部门工资排名前二的员工,可以把PARTITION BY和RANK结合起来:

WITH ranked AS (
    SELECT
        emp_name,
        dept_id,
        salary,
        RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rnk
    FROM employee
)
SELECT emp_name, dept_id, salary
FROM ranked
WHERE rnk <= 2;

注意这里如果用ROW_NUMBER,遇到并列第二的员工就只会保留一个,可能不符合业务预期。如果希望并列的都保留,RANK或者DENSE_RANK才是正确选择。可见函数选型本身就是一个业务判断,不是单纯的技术问题。

常见错误与性能优化建议

第一个高频错误是遗漏ORDER BY。在DB2中,ROW_NUMBER的OVER子句如果不写ORDER BY,语法上是允许的,此时编号按照系统返回的任意顺序分配,结果不可预测。有些开发者误以为不写ORDER BY就等于随机取一条,实际上这个随机既不均匀也不可复现,用它做随机抽样是不可靠的,随机需求应该使用RAND函数配合排序来实现。

第二个问题是分区字段选择错误。比如按部门分组排名时误把公司级字段放进PARTITION BY,或者反过来漏掉了分组维度,导致排名结果跨组混乱。建议写完SQL后先用小数据量验证,确认每个分区内的编号都从1开始重新计数,再放到生产环境执行。

性能方面,窗口函数本质上依赖排序操作,所以PARTITION BY和ORDER BY涉及的列上如果有合适的索引,DB2可以直接利用索引避免显式排序,性能会好很多。对于频繁执行的分组取最新记录这类查询,可以考虑在分区列加排序列上建立复合索引。此外,尽量把不需要参与窗口计算的行先在WHERE中过滤掉,减少进入排序的数据量,这比事后在外层过滤编号要高效得多。

最后一点关于兼容性:ROW_NUMBER、RANK、DENSE_RANK都是SQL标准函数,在DB2、Oracle、SQL Server、MySQL 8.0中写法基本一致,掌握之后可以平滑迁移到其他数据库。但一些扩展窗口函数如LISTAGG OVER的细节在各平台存在差异,跨库迁移时需要逐一验证。熟练运用窗口函数后,你会发现过去很多用自关联、多层子查询实现的复杂逻辑,都可以用一条清晰的窗口函数语句重写,代码可读性和执行效率都会有明显提升。

DB2窗口函数ROW_NUMBERRANK修改时间:2026-09-13 04:30:30

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260913/55763.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。