MySQL 8.0原生支持RANK()和DENSE_RANK()窗口函数,需配合OVER(ORDER BY ...)使用,区别在于并列时RANK()跳号而DENSE_RANK()不跳号,且二者均须用于SELECT列表,不可出现在WHERE或GROUP BY中。

MySQL 8.0里RANK()和DENSE_RANK()怎么写
MySQL 8.0原生支持窗口函数,RANK()和DENSE_RANK()可以直接用,不需要自连接或变量模拟。关键是要在SELECT中调用,并配合OVER()子句定义排序逻辑。
基本写法是:RANK() OVER (ORDER BY score DESC) 或 DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC)。注意OVER里必须有ORDER BY,否则会报错Window 'w' lacks an ORDER BY clause。
常见错误:把RANK()写在WHERE或GROUP BY里——窗口函数不能出现在这些子句中,只能用于SELECT列表或HAVING(需搭配聚合)。
RANK()和DENSE_RANK()到底差在哪
区别只在并列时的编号处理方式,其他完全一致:
-
RANK():并列名次后跳过后续编号。比如两个第1名,则下一个是第3名(跳过2) -
DENSE_RANK():并列名次后不跳号。两个第1名,下一个是第2名
示例数据(按score降序):
score | RANK() | DENSE_RANK() 95 | 1 | 1 95 | 1 | 1 87 | 3 | 2 82 | 4 | 3
如果业务要求“前N名”且允许并列(如发奖名单),DENSE_RANK()更直观;若强调“实际排位顺序”(比如淘汰制晋级名额),RANK()更符合传统体育排名逻辑。
分组内排名必须加PARTITION BY
想按部门分别排工资名次?漏掉PARTITION BY dept_id就会变成全表统一排序,这是新手最常踩的坑。
正确写法:
SELECT name, dept_id, salary, RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rnk FROM employees;
注意事项:
-
PARTITION BY字段值为NULL会被归为同一组,不是被忽略 - 不能在同一个
OVER()里混用PARTITION BY和未分区的窗口函数 - 性能上,
PARTITION BY字段最好有索引,尤其数据量大时;否则MySQL可能无法利用索引加速排序
和ROW_NUMBER()混用容易出错
三者常被一起比较,但行为差异明显:ROW_NUMBER()严格按顺序编号(1,2,3…),不管值是否重复;RANK()和DENSE_RANK()才真正处理并列。
典型误用场景:
- 用
ROW_NUMBER()实现“取每组最高分1人”,结果可能漏掉并列最高分者 - 用
RANK()做分页(如WHERE rnk BETWEEN 11 AND 20),因跳号导致实际返回行数少于10
如果需要稳定分页且保留并列,优先选DENSE_RANK();如果必须唯一编号(比如生成连续ID),才用ROW_NUMBER()。
窗口函数的执行时机晚于GROUP BY和HAVING,但早于ORDER BY(外部)和LIMIT——这意味着LIMIT会截断结果,但不会影响窗口计算过程本身。

















