MySQL 8.0+ 推荐用窗口函数实现排名,ROW_NUMBER()、RANK()、DENSE_RANK() 各具语义且稳定;5.7 及更早需依赖不可靠的用户变量,自连接方案性能差且 NULL 处理不一致。

MySQL 8.0+ 直接用 ROW_NUMBER()、RANK() 或 DENSE_RANK() 就能实现,无需变量或子查询嵌套;5.7 及更早版本必须用用户变量,但存在执行顺序不可靠的风险。
用窗口函数(MySQL 8.0+)最稳,别再折腾变量
MySQL 8.0 引入标准 SQL 窗口函数,ROW_NUMBER() 按顺序编号(1,2,3…),RANK() 处理并列时跳号(1,1,3…),DENSE_RANK() 并列不跳号(1,1,2…)。三者都支持 ORDER BY 和可选的 PARTITION BY。
常见错误是漏写 OVER() 的括号或误把排序写在外部 ORDER BY —— 窗口函数的排序必须在 OVER(ORDER BY ...) 里定义,外部 ORDER BY 只影响最终结果顺序,不影响排名逻辑。
示例(按销售额降序排名):
SELECT name, sales, ROW_NUMBER() OVER (ORDER BY sales DESC) AS row_num, RANK() OVER (ORDER BY sales DESC) AS rank_num, DENSE_RANK() OVER (ORDER BY sales DESC) AS dense_rank_num FROM sales_data;
MySQL 5.7 用变量模拟排名,但必须加 ORDER BY 强制排序
变量方案本质是“伪窗口函数”,依赖 SELECT 执行时的行处理顺序。如果没显式 ORDER BY,MySQL 可能按任意物理顺序遍历行,导致排名错乱 —— 这是最常踩的坑。
正确写法必须确保排序在变量赋值前完成,且变量初始化和递增写在同一 SELECT 中:
清理、标准化、验证并汇总杂乱的 Excel 工作簿。当 Codex 需要处理 `.xlsx`、`.xls`、`.xlsm` 或 `.csv` 格式的数据时使用,包括:统一表头格式、删除空白行与空白列、修剪文本内容、去重记录、检测数据质量问题、拆分或合并工作表、生成清洗后的工作簿,以及为业务电子表格生成审计摘要。
-
@rank := @rank + 1实现连续编号(类似ROW_NUMBER()) -
@rank := IF(@prev = score, @rank, @rank + 1)配合@prev := score实现RANK()逻辑 - 变量必须在 SELECT 前用
SELECT @rank := 0, @prev := NULL初始化,否则首次计算可能为 NULL
示例(兼容 5.7,按 score 降序排名):
SELECT name, score, @rank := @rank + 1 AS row_num FROM ( SELECT name, score FROM scores ORDER BY score DESC ) t, (SELECT @rank := 0) r;
为什么不能用 JOIN 自关联实现排名?
自连接统计比当前行大的记录数(SELECT COUNT(*) FROM t t2 WHERE t2.score > t1.score)理论上可行,但性能极差:N 行数据会触发 N² 次扫描,10 万行就可能秒变秒级延迟。窗口函数是 O(N log N),变量方案是 O(N),而自连接是 O(N²) —— 数据量稍大就扛不住。
另一个问题是 NULL 值处理:自连接中 score > NULL 永远为 FALSE,会导致 NULL 分数被排在最前(或最后,取决于写法),而 RANK() 默认把 NULL 当最小值处理,行为不一致。
分区排名(如各班级内排名)要注意 PARTITION BY 位置
窗口函数的 PARTITION BY 必须写在 OVER 内部,且在 ORDER BY 前 —— 顺序错了会报语法错误。例如按 class_id 分组、每组内按 score 排名:
SELECT class_id, name, score, ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY score DESC) AS rank_in_class FROM students;
变量方案无法自然支持分区,必须靠子查询或应用层分组,代码复杂度陡增。如果业务需要动态分区(比如按日期、地区等多维度),强烈建议升级到 MySQL 8.0+。
真正容易被忽略的是:窗口函数在视图或子查询中可以复用,而变量方案一旦嵌套就失效 —— 因为变量作用域只限于单个 SELECT 语句。线上环境如果已有复杂查询链路,改用变量反而更容易出错。

















