RANK()与DENSE_RANK()结果不同是因为并列时跳位逻辑不同:RANK()跳过后续名次(如1,1,3,4),DENSE_RANK()不跳(如1,1,2,3);二者均须显式ORDER BY且不可用于WHERE或GROUP BY中。

为什么 RANK 和 DENSE_RANK 的结果经常不一样?
因为它们处理「相同值并列」后的跳位逻辑完全不同。RANK() 遇到并列会跳过后续名次,比如两个第1名后直接是第3名;DENSE_RANK() 则不跳,两个第1名后就是第2名。这不是 bug,是设计意图——前者强调“名次位置”,后者强调“等级层次”。
常见错误现象:RANK() 返回 1,1,3,4,你误以为漏了第2名;DENSE_RANK() 返回 1,1,2,3,你却拿它去算“前3名用户”时漏掉并列者。
- 使用场景:
RANK()适合模拟体育比赛颁奖(并列金牌,没人拿银牌);DENSE_RANK()更适合内部绩效分级(A级、B级、C级,不因人数多就跳级) - 二者都必须搭配
OVER (ORDER BY ...),且不能单独出现在 WHERE 或 GROUP BY 中 - 性能上无实质差异,但若 ORDER BY 字段未建索引,大数据量下都会明显变慢
怎么写才不会触发“窗口函数只能在 SELECT 和 ORDER BY 中使用”错误?
这个错误(如 PostgreSQL 报 window function is not allowed in WHERE clause)本质是把窗口函数当成了普通标量函数用。你不能在 WHERE、GROUP BY 或 HAVING 里直接调 RANK(),因为这些子句执行时,窗口计算还没发生。
正确做法是:先用子查询或 CTE 把排名算出来,再对外层结果过滤。
SELECT user_id, score, rk FROM ( SELECT user_id, score, RANK() OVER (ORDER BY score DESC) AS rk FROM scores ) t WHERE rk <= 3;
- 别写
WHERE RANK() OVER (...) —— 语法直接报错 - 如果还要按排名分页(比如查第2页,每页5人),必须在外层再套一层,否则
OFFSET会作用在未排名的原始行上 - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;SQLite 目前不支持窗口函数
ORDER BY 里加多个字段会影响并列判断吗?
会,而且影响方式很关键:只有所有 ORDER BY 字段值完全相同时,才算并列。比如 ORDER BY score DESC, created_at ASC,两个用户分数相同但创建时间不同,就不会并列,RANK() 和 DENSE_RANK() 都会给不同名次。
想强制让同分者一定并列?那就只按 score 排序,其他字段放到 ORDER BY 外面,比如作为最终结果的二级排序:
SELECT user_id, score,
RANK() OVER (ORDER BY score DESC) AS rk
FROM scores
ORDER BY score DESC, user_id ASC;
-
OVER (ORDER BY score DESC)决定谁并列、怎么排名 - 外层
ORDER BY只控制最终输出顺序,不影响排名计算逻辑 - 如果业务要求“同分时按时间早者优先占高位”,那必须把
created_at加进OVER子句,并用ASC—— 否则默认稳定但不可控
NULL 值在排名中怎么算?
默认情况下,NULL 被视为“最大值”(在 ORDER BY ... DESC 时排最前,在 ASC 时排最后),所以可能拿到第1名或最后一名,且和其他 NULL 并列。这常导致误判——你以为没数据,其实是 NULL 占了前几名。
解决方法不是删 NULL,而是显式控制它的位置:
SELECT RANK() OVER ( ORDER BY score DESC NULLS LAST ) AS rk FROM scores;
- PostgreSQL、Oracle、SQL Server 支持
NULLS FIRST / NULLS LAST;MySQL 8.0 不支持该语法,需用CASE WHEN score IS NULL THEN 1 ELSE 0 END拆解 - 如果业务上 NULL 表示“未参与”,那它就不该参与排名——得先
WHERE score IS NOT NULL - 别依赖数据库默认行为,尤其跨库迁移时,NULL 排序策略可能不一致

















