当前位置:首页 > 文章列表 > 数据库 > MySQL > 窗口函数做分组排名时,ROW_NUMBER 与 DENSE_RANK 怎么选

窗口函数做分组排名时,ROW_NUMBER 与 DENSE_RANK 怎么选

来源:17golang原创 2026-10-07 10:58:17 0浏览 收藏

选择其实只看业务问题:如果你要“每个分组固定取前 N 行”,用 ROW_NUMBER();如果你要“每个分组保留前 N 个名次档位,并列记录全部保留”,用 DENSE_RANK()。两者都在分区内排名,但对同分记录的处理完全不同。

我以前写部门 Top 2 时,最容易忽略的不是函数名,而是“Top 2”到底指两名员工,还是两个分数档位。前一种需求要求结果数量稳定;后一种需求必须尊重并列,返回行数可能超过 2。先把这句话问清楚,SQL 基本就选对了一半。

同分数据为什么最能看出差别

准备一张季度成绩表。每个部门都有两位员工同分,这样 ROW_NUMBER() 与 DENSE_RANK() 的差异会直接出现。

-- 建立部门季度成绩示例表
CREATE TABLE quarterly_scores (
    id BIGINT PRIMARY KEY,
    department VARCHAR(32) NOT NULL,
    employee VARCHAR(32) NOT NULL,
    score INT NULL,
    submitted_at DATETIME NOT NULL
);

-- 插入两组带并列分数的原创示例数据
INSERT INTO quarterly_scores
    (id, department, employee, score, submitted_at)
VALUES
    (1, '研发', '安然', 98, '2026-09-30 10:00:00'),
    (2, '研发', '博文', 95, '2026-09-30 09:00:00'),
    (3, '研发', '晨曦', 95, '2026-09-30 08:00:00'),
    (4, '研发', '东海', 90, '2026-09-29 17:00:00'),
    (5, '销售', '方晴', 100, '2026-09-30 11:00:00'),
    (6, '销售', '高远', 97, '2026-09-30 10:30:00'),
    (7, '销售', '海宁', 97, '2026-09-30 09:30:00'),
    (8, '销售', '佳音', 88, '2026-09-29 16:00:00');

每个部门的最高分只有一人,第二高分有两人。如果业务说“奖励每个部门两人”,并列时仍然只能选两行;如果业务说“奖励前两个成绩档位”,那么第二档的两人都要保留,每个部门会返回三行。

同一份排序,两个函数回答不同问题

成绩表、部门分区、分数排序、稳定排序列与ROW_NUMBER和DENSE_RANK的静态查询结构
图1:分组排名查询的静态结构说明图;同一个部门分区和分数排序分别连接 ROW_NUMBER 与 DENSE_RANK,不代表数据库执行流程。

MySQL 官方文档把 ROW_NUMBER() 定义为分区内当前行的编号:即使两行在窗口排序值上相同,也会得到不同编号。DENSE_RANK() 则把同序值视为并列,为它们分配相同名次,而且后续名次不留空档。

比较点ROW_NUMBERDENSE_RANK
同分记录仍分配不同序号分配相同名次
名次是否连续每行依次递增并列组之间连续
筛选前 N每组最多 N 行每组可能超过 N 行
典型用途去重、每组固定 Top N等级、榜单并列、分数档位

需要固定两行时用 ROW_NUMBER

如果报表版位、名额或后续批处理要求每个部门恰好两行,我会用 ROW_NUMBER(),并把可重复值之后的稳定排序列写完整。下面先按分数降序,再按提交时间升序,最后用主键兜底。

WITH ranked AS (
    SELECT
        id,
        department,
        employee,
        score,
        submitted_at,
        -- 同分时先提交者优先,主键负责最终稳定排序
        ROW_NUMBER() OVER (
            PARTITION BY department
            ORDER BY score DESC, submitted_at ASC, id ASC
        ) AS rn
    FROM quarterly_scores
    WHERE score IS NOT NULL
)
SELECT id, department, employee, score, submitted_at, rn
FROM ranked
WHERE rn 

研发组会选 98 分的安然和 95 分中提交更早的晨曦;销售组会选 100 分的方晴和 97 分中提交更早的海宁。这里的“先提交者优先”只是示例规则,实际项目可以换成更新时间、业务优先级或唯一主键,但必须让规则和需求一致。

我觉得 ROW_NUMBER() 最大的好处不是“没有并列”,而是结果基数可控。它的代价也很明确:遇到同分时必须人为决定谁先谁后;如果业务认为同分绝对平等,这种截断就会丢掉一部分并列记录。

需要保留并列档位时用 DENSE_RANK

如果排行榜展示的是成绩等级,第二名有几个人就应该展示几个人,我会改用 DENSE_RANK()。关键细节是:窗口里的 ORDER BY 只能放定义“同一档位”的业务字段。若把 id 也放进去,每行排序组合都不同,并列就被拆散了。

WITH ranked AS (
    SELECT
        id,
        department,
        employee,
        score,
        submitted_at,
        -- 名次只由分数决定,同分员工必须得到相同名次
        DENSE_RANK() OVER (
            PARTITION BY department
            ORDER BY score DESC
        ) AS dr
    FROM quarterly_scores
    WHERE score IS NOT NULL
)
SELECT id, department, employee, score, submitted_at, dr
FROM ranked
WHERE dr 

这时每个部门都返回三行:最高分一行,第二高分两行。DENSE_RANK() 的名次是 1、2、2、3,不会因为第二档有两人而跳到 4。它很适合“前两个等级”“前两个价格档”“前两个不同分数”这类需求,但不保证固定返回两条记录。

前两行和前两个档位,返回数量可能不同

ROW_NUMBER固定两行与DENSE_RANK保留并列档位后可变行数的静态关系
图2:Top N 结果语义的静态关系说明图;左侧强调固定两行,右侧强调保留两个分数档位时可能返回多行。

这正是我第一次在报表里踩坑的地方:SQL 看起来都像“分组后取前两名”,但一个控制行数,一个控制档位。评审需求时可以直接问下面两句话:

  • 同分时是否允许多返回几行?允许,就倾向 DENSE_RANK()。
  • 下游是否要求每组最多 N 行?要求,就使用 ROW_NUMBER() 并明确同分决胜列。

如果既要尊重并列,又必须限制总行数,就不能只靠一个排名函数解决。需要在业务层定义截断策略,例如先按档位选候选,再用容量、优先级或抽签规则二次选择。不要悄悄用主键拆散并列,却仍把结果描述为“同分同名次”。

几个容易误判的边界

没有窗口 ORDER BY 会怎样?

官方文档指出,没有 ORDER BY 时,ROW_NUMBER() 的编号顺序是不确定的;对 DENSE_RANK() 来说,没有排序时所有行都是 peers,也就是同一名次。排名查询应明确窗口内排序,不要把最终结果集的外层 ORDER BY 误当成窗口排序。

为什么不能直接在同一层 WHERE 里写 rn

窗口计算发生在 WHERE、GROUP BY 和 HAVING 之后。窗口函数可以出现在选择列表和查询级 ORDER BY 中,因此筛选窗口别名通常要放到 CTE 或派生表的外层。这也是上面两个示例都先构造 ranked 再过滤的原因。

NULL 分数怎么排?

MySQL 窗口排序里,NULL 在升序时排前、降序时排后。示例直接用 WHERE score IS NOT NULL 排除未评分记录,因为“未评分”通常不应进入成绩排名。如果业务要保留它们,应单独定义未评分展示区,而不是默认把它们当作最低分。

DENSE_RANK 的 ORDER BY 能加多个字段吗?

可以,但每增加一个字段,peer 的定义就更严格。只有所有排序表达式都相同的行才会并列。若业务名次只由分数决定,窗口中就只放分数;提交时间和主键应放在外层展示排序中。若业务明确规定“分数相同再按完成时长分档”,才把完成时长加入 DENSE_RANK() 的窗口排序。

我的选择清单

  • 固定每组 N 行、选最新一条、组内去重:ROW_NUMBER()。
  • 保留前 N 个不同分数或等级、同分全部展示:DENSE_RANK()。
  • ROW_NUMBER() 的窗口排序补齐唯一决胜列,避免同分顺序漂移。
  • DENSE_RANK() 的窗口排序只保留真正定义档位的字段,别用主键破坏并列。
  • 窗口别名放到 CTE 或派生表外层过滤,外层 ORDER BY 只控制最终展示。

对我来说,最实用的判断不是背函数定义,而是先写出结果基数:到底必须返回两行,还是允许第二档并列后变成三行。前者选 ROW_NUMBER(),后者选 DENSE_RANK();剩下的工作就是把分区字段、业务排序和稳定展示规则写准确。

延伸问题

RANK 与 DENSE_RANK 又有什么区别?

两者都让 peers 共享名次;RANK() 在并列后会留下名次空档,DENSE_RANK() 不留空档。若成绩序列是 100、97、97、88,二者分别给出 1、2、2、4 和 1、2、2、3。

每组取最新一条为什么更适合 ROW_NUMBER?

因为目标是每组只保留一行。按时间降序并用主键兜底后,筛选 rn = 1 可以稳定得到唯一记录;DENSE_RANK() 在时间相同的情况下可能保留多行。

分组排名能不能直接用 LIMIT?

普通 LIMIT 限制的是整个结果集,不会为每个部门分别计数。每组 Top N 需要先通过 PARTITION BY 在组内产生排名,再在外层按排名值过滤。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
为二进制协议解析器添加种子语料与不变量断言为二进制协议解析器添加种子语料与不变量断言
上一篇
为二进制协议解析器添加种子语料与不变量断言
模糊测试只跑到少量路径,怎样改善种子语料质量
下一篇
模糊测试只跑到少量路径,怎样改善种子语料质量
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    363次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    419次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    433次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    386次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    213次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码