当前位置: 首页 > news >正文

别再傻傻分不清了!用MySQL 8.0实战演示row_number、rank、dense_rank到底怎么选

MySQL 8.0排名函数实战指南:row_number、rank、dense_rank的智能选择

每次面对需要排名的SQL查询时,你是否也在纠结该用哪个窗口函数?row_number、rank还是dense_rank?这三个看似相似的函数,在实际业务场景中却有着截然不同的表现。让我们通过一个电商平台的真实案例,彻底搞懂它们之间的区别和适用场景。

1. 电商排名场景搭建与基础概念

假设我们正在为某电商平台开发用户积分排行榜功能,数据表结构如下:

CREATE TABLE user_points ( user_id INT PRIMARY KEY, username VARCHAR(50) NOT NULL, points INT NOT NULL, registration_date DATE ); -- 插入测试数据 INSERT INTO user_points VALUES (101, '数码达人', 8500, '2023-01-15'), (102, '时尚买手', 9200, '2023-02-20'), (103, '家居专家', 9200, '2023-03-10'), (104, '美妆教主', 7800, '2023-01-05'), (105, '美食家', 9200, '2023-04-01'), (106, '旅行家', 8100, '2023-02-28'), (107, '图书爱好者', 7600, '2023-03-15'), (108, '运动健将', 8900, '2023-01-20');

1.1 窗口函数基础语法

所有三个排名函数都遵循相同的语法模式:

函数名() OVER ( [PARTITION BY 分组字段] ORDER BY 排序字段 [ASC|DESC] )
  • PARTITION BY:可选,用于先分组再组内排序
  • ORDER BY:必需,指定排序依据的字段和方向

提示:在MySQL 8.0+中,窗口函数性能已大幅优化,但大数据量时仍需注意合理使用索引。

2. 三大排名函数深度对比

2.1 row_number:严格的顺序编号

row_number()为每一行分配唯一的连续序号,即使排序值相同:

SELECT user_id, username, points, ROW_NUMBER() OVER (ORDER BY points DESC) AS rank FROM user_points;

执行结果:

user_idusernamepointsrank
102时尚买手92001
103家居专家92002
105美食家92003
108运动健将89004
101数码达人85005
106旅行家81006
104美妆教主78007
107图书爱好者76008

核心特点

  • 绝对唯一的连续编号
  • 相同points值的用户获得不同rank值
  • 适合需要严格区分名次的场景

2.2 rank:允许并列的真实排名

rank()函数会在值相同时给相同排名,但会保留排名间的"空隙":

SELECT user_id, username, points, RANK() OVER (ORDER BY points DESC) AS rank FROM user_points;

执行结果:

user_idusernamepointsrank
102时尚买手92001
103家居专家92001
105美食家92001
108运动健将89004
101数码达人85005
106旅行家81006
104美妆教主78007
107图书爱好者76008

关键区别

  • 三个9200分的用户并列第1名
  • 下一个用户直接跳到第4名(跳过了2、3)
  • 符合传统体育比赛的排名方式

2.3 dense_rank:紧凑的连续排名

dense_rank()与rank()类似允许并列,但排名数字是连续的:

SELECT user_id, username, points, DENSE_RANK() OVER (ORDER BY points DESC) AS rank FROM user_points;

执行结果:

user_idusernamepointsrank
102时尚买手92001
103家居专家92001
105美食家92001
108运动健将89002
101数码达人85003
106旅行家81004
104美妆教主78005
107图书爱好者76006

显著特征

  • 并列第一后,下一个是第二而非第四
  • 排名数字连续无间隔
  • 总排名数量最少

3. 业务场景选择指南

3.1 何时选择row_number?

适用场景

  1. 需要绝对唯一的标识符(如分页查询)
  2. 生成连续的行号(如导出报表)
  3. 不允许并列的严格排名(如某些竞赛规则)

电商案例

-- 为每个用户生成唯一的会员编号(基于注册时间) SELECT user_id, username, ROW_NUMBER() OVER (ORDER BY registration_date) AS member_number FROM user_points;

3.2 何时选择rank?

适用场景

  1. 体育比赛排名(允许并列且保留名次间隔)
  2. 需要显示实际排名位置的场景
  3. 当业务逻辑要求反映"真实"排名时

电商案例

-- 显示用户的实际排名(考虑并列情况) SELECT username, points, RANK() OVER (ORDER BY points DESC) AS global_rank FROM user_points WHERE RANK() OVER (ORDER BY points DESC) <= 10; -- 获取前10名

注意:直接在WHERE中使用窗口函数会报错,需要使用子查询或CTE。

3.3 何时选择dense_rank?

适用场景

  1. 需要紧凑连续的排名数字
  2. 奖品分配按排名层级而非绝对位置
  3. 当排名间隔可能引起误解时

电商案例

-- 按排名层级发放不同级别的优惠券 SELECT username, points, DENSE_RANK() OVER (ORDER BY points DESC) AS tier FROM user_points WHERE DENSE_RANK() OVER (ORDER BY points DESC) <= 3; -- 获取前三档用户

4. 高级应用与性能优化

4.1 分组排名实战

窗口函数的真正威力在于分组排名:

-- 按注册月份分组后的积分排名 SELECT user_id, username, points, DATE_FORMAT(registration_date, '%Y-%m') AS reg_month, ROW_NUMBER() OVER (PARTITION BY DATE_FORMAT(registration_date, '%Y-%m') ORDER BY points DESC) AS month_rank FROM user_points;

4.2 多维度排序技巧

当主要排序字段相同时,可以添加次要排序条件:

-- 积分相同则按注册时间排序 SELECT user_id, username, points, registration_date, RANK() OVER (ORDER BY points DESC, registration_date) AS rank FROM user_points;

4.3 性能优化建议

  1. 索引策略

    • 为PARTITION BY和ORDER BY涉及的字段创建复合索引
    • 示例:CREATE INDEX idx_points_reg ON user_points(points DESC, registration_date)
  2. 减少计算量

    -- 先过滤再排名,提高性能 WITH filtered_users AS ( SELECT * FROM user_points WHERE points > 8000 ) SELECT user_id, username, points, RANK() OVER (ORDER BY points DESC) AS rank FROM filtered_users;
  3. 避免重复计算

    -- 使用CTE避免多次计算相同窗口 WITH ranked_users AS ( SELECT user_id, username, points, RANK() OVER (ORDER BY points DESC) AS rank, DENSE_RANK() OVER (ORDER BY points DESC) AS dense_rank FROM user_points ) SELECT * FROM ranked_users WHERE rank <= 5;

5. 决策树与常见误区

5.1 函数选择决策树

是否需要绝对唯一的序号? ├── 是 → 使用row_number └── 否 → 是否允许排名并列? ├── 否 → 使用row_number └── 是 → 是否需要紧凑连续的排名数字? ├── 是 → 使用dense_rank └── 否 → 使用rank

5.2 常见错误与解决方法

问题1:在WHERE中直接使用窗口函数

-- 错误写法 SELECT * FROM user_points WHERE RANK() OVER (ORDER BY points DESC) <= 3; -- 正确写法 SELECT * FROM ( SELECT *, RANK() OVER (ORDER BY points DESC) AS rank FROM user_points ) AS ranked WHERE rank <= 3;

问题2:忽略NULL值的影响

-- NULL值默认会排在最后(ASC)或最前(DESC) -- 可以使用COALESCE处理 SELECT user_id, username, points, RANK() OVER (ORDER BY COALESCE(points, 0) DESC) AS rank FROM user_points;

问题3:性能问题大数据集

-- 对于大型表,考虑添加LIMIT SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY points DESC) AS rank FROM user_points -- 先限制数据量再排序 LIMIT 1000 ) AS ranked WHERE rank <= 100;

在实际项目中,我发现很多开发者会过度使用row_number,仅仅因为它是第一个学到的窗口函数。但理解这三个函数的本质区别后,你会发现每种业务场景都有最适合的选择。比如在做分页查询时row_number是必须的,但在展示排行榜时rank或dense_rank通常更符合用户预期。

http://www.cnnetsun.cn/news/2151538.html

相关文章:

  • 2026届最火的五大AI写作平台推荐榜单
  • 2025届毕业生推荐的十大AI辅助论文神器实测分析
  • 分钟搞懂深度学习AI:毁掉AI的广播机制陷阱
  • STM32电子罗盘DIY:用ST480MC磁力计和IIC接口,手把手教你做个指南针(附校准避坑指南)
  • VMware 17 + Win11 最佳拍档:不止是安装,更是高效开发环境搭建指南
  • DLSS Swapper终极指南:专业级游戏性能优化解决方案
  • 如何用Vue流程图组件Flowchart-Vue快速构建专业业务流程可视化
  • 从零开始:手把手教你为STM32H7系列MCU配置Cortex-M7的TCM与Cache(附性能对比)
  • 从TDengine IDMP看资产与事件驱动的可视化:从仪表板到运营洞察
  • 内网渗透核心技术:内网代理从原理到实战全解析
  • C# 13内联数组性能真相(Stack-Only Array大揭秘):为什么.NET Runtime团队禁用常规new操作符?
  • 人人选商城便捷的哪个好
  • 终极指南:TrollInstallerX iOS越狱工具一键安装TrollStore完整方案
  • ARM AMAIR0寄存器:内存属性优化与安全配置详解
  • 深度学习在材料显微图像分析中的应用与优化
  • 空间数据流架构:深度学习加速器的核心设计与优化
  • 别再手动传文件了!用NFS在Linux服务器间搭个共享文件夹(CentOS 7实战)
  • 强化学习工具规划与GRPO算法实践指南
  • 用W801和AD7124搞定PT100高精度测温:从寄存器配置到温度换算的保姆级教程
  • 从单周期到五段流水:手把手教你用Verilog在FPGA上实现MIPS CPU(附完整代码与避坑指南)
  • Python实战:用NumPy和Matplotlib绘制标准正态分布曲线(附完整代码)
  • Docker部署Nginx时SSL证书报错?别慌,可能是挂载路径的‘坑’
  • 游戏开发者的字体合并实战:用FontForge搞定Unity多语言显示(附避坑指南)
  • 深入解读Xilinx QDMA的dma-ctl工具:从设备管理到性能调优的完全指南
  • CANoe仿真面板避坑指南:从系统变量关联到Desktop布局,新手常踩的5个雷我都帮你排了
  • CVPR2023 RIDCP论文精读:从‘SwinIR编码器’到‘可控先验匹配’,拆解一个SOTA去雾网络的工程细节
  • ESP32-S3-Pico + OV7725摄像头:手把手教你用Arduino IDE搞定图像采集与串口传输(附完整代码)
  • 从MovieLens用户画像到精准推荐:手把手教你用Python完成用户分群全流程
  • 5秒完成B站视频永久保存:m4s-converter让你珍藏的缓存不再失效
  • Cursor Free VIP:从技术限制到无限可能的开发者解放之路