导读:本期聚焦于小伙伴创作的《如何在MySQL 8.0中获取分组内的前三名数据?使用DENSE_RANK()窗口函数实战解析》,敬请观看详情。想从每个商品分类里捞出销量最高的三款产品,却发现用GROUP BY根本拿不到明细行?窗口函数里的DENSE_RANK()正好能解决这种分组排名问题。它在不碰撞行数的前提下,为同组相同值分配连续名次,再配合子查询过滤就能稳定取出前三名。相比早期用变量模拟排名写法,DENSE_RANK可读性和维护性都更好,也不会因并列值导致名次断裂。下面以销售表为例,演示建表、写排名SQL以及边界情况处理,帮你直接套用到报表统计场景。

在业务报表中,我们常常需要按某个维度分组,并取出每组内部排名靠前的几条记录。比如按店铺分组取营业额前三的商品,或按班级分组取分数前三的学生。MySQL 8.0 提供了标准的窗口函数,其中 DENSE_RANK() 能够优雅地处理并列名次并连续排名,是完成该需求的推荐方案。

如何在MySQL 8.0中获取分组内的前三名数据?使用DENSE_RANK()窗口函数实战解析

一、什么是 DENSE_RANK() 窗口函数

DENSE_RANK() 是 SQL 标准中的窗口排序函数,它会根据 ORDER BY 子句在分区(PARTITION BY)内为每一行计算一个排名。与 RANK() 不同,DENSE_RANK() 在出现并列值时不会跳过后续名次。例如两个并列第一,下一个仍然是第二,而不是第三。这种特性在“取前三名”且允许并列的场景中非常实用。

从底层逻辑看,窗口函数并不会将多行聚合成一行,而是在保留原表所有行的同时,额外计算出一个排名列。执行时 MySQL 会先按 PARTITION BY 分组,再在组内按 ORDER BY 排序,最后通过滑动计数方式赋予密集排名。由于不改变行数,我们可以直接在外部查询中用 WHERE 条件筛选排名小于等于 3 的记录。

二、准备示例数据

为了直观演示,我们创建一个销售记录表,包含门店编号、商品名称和销售数量三个字段,并插入一些测试数据,其中包含同门店同销量的并列情况。

CREATE TABLE sales (
    store_id INT,
    product_name VARCHAR(50),
    quantity INT
);

INSERT INTO sales (store_id, product_name, quantity) VALUES
(1, 'A', 100),
(1, 'B', 100),
(1, 'C', 90),
(1, 'D', 80),
(2, 'X', 50),
(2, 'Y', 50),
(2, 'Z', 30),
(2, 'W', 20);

上述数据中,门店 1 的 A 和 B 销量都是 100,应当并列第一;门店 2 的 X 和 Y 都是 50,也应并列第一。如果我们希望取出每个门店销量前三名的商品(含并列),DENSE_RANK() 会非常合适。

三、使用 DENSE_RANK() 获取分组前三名

下面通过子查询将 DENSE_RANK() 计算出的排名作为临时列,再在外层过滤出排名不超过 3 的行。注意窗口函数不能直接写在 WHERE 中,因此必须包一层查询。

SELECT
    store_id,
    product_name,
    quantity,
    rnk
FROM (
    SELECT
        store_id,
        product_name,
        quantity,
        DENSE_RANK() OVER (
            PARTITION BY store_id
            ORDER BY quantity DESC
        ) AS rnk
    FROM sales
) t
WHERE t.rnk <= 3
ORDER BY store_id, rnk;

执行后,门店 1 会返回 A、B(并列第一)、C(第二)、D(第三);门店 2 会返回 X、Y(并列第一)、Z(第二)、W(第三)。如果改用 RANK(),并列第一后下一名会变成第三,从而可能漏掉真正的“第二”,这正是 DENSE_RANK 的优势。

在性能方面,MySQL 8.0 对窗口函数做了优化,通常会使用内存临时表完成分区排序。当数据量较大时,建议为 PARTITION BY 和 ORDER BY 涉及的列建立联合索引,例如本例可建 (store_id, quantity) 索引,以减少排序开销。

四、与变量模拟写法的对比

在 MySQL 5.7 及更早版本没有窗口函数时,开发者常用用户变量模拟排名。这种写法不仅容易出错,而且在并列处理和分组边界上需要写大量 CASE 逻辑。

SELECT store_id, product_name, quantity, rnk
FROM (
    SELECT
        store_id, product_name, quantity,
        @r := IF(@s = store_id, @r + 1, 1) AS rnk,
        @s := store_id
    FROM sales, (SELECT @s := NULL, @r := 0) v
    ORDER BY store_id, quantity DESC
) t
WHERE rnk <= 3;

上面的变量写法只能生成普通连续序号,无法天然处理并列密集排名,若要模拟 DENSE_RANK 还需额外嵌套。而窗口函数版本语义清晰、不易写错,也方便数据库优化器生成更优执行计划。因此在 MySQL 8.0 环境中,应优先使用 DENSE_RANK()。

五、常见注意事项

首先,DENSE_RANK() 计算的是“密集排名”,如果业务要求“最多返回三行”而不是“排名前三”,则应改用 ROW_NUMBER()。其次,ORDER BY 子句建议显式写明排序方向,避免默认升序导致取成倒数前三。最后,在子查询别名和外层过滤时,确保排名列名不与保留字冲突。

当分组内不同记录排序值相同时,DENSE_RANK 会赋予相同名次,这可能导致最终返回行数超过预期的三行。若产品经理想要严格限制行数,需结合业务确认是否允许并列扩张,或改用其他函数。理解这些差异,才能让分组取前 N 名的 SQL 稳定服务于生产报表。

MySQL_8.0DENSE_RANK窗口函数修改时间:2026-08-07 14:09:27

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