DENSE_RANK()保证名次连续不跳号是因为其“值变才加1”的逻辑:相同排序值共享同一排名,值变化时仅+1;而RANK()统计前面不同值个数导致跳号。

为什么DENSE_RANK()能保证名次连续不跳号
DENSE_RANK() 的行为逻辑是“值变才加 1”,不是按行数或位置递增。只要 ORDER BY 中的排序字段值相同,所有行就共享同一个名次;一旦值下降(或上升,取决于 ASC/DESC),名次才 +1。比如分数为 [100, 100, 95, 88],结果就是 [1, 1, 2, 3]。这和 RANK()(输出 [1, 1, 3, 4])有本质区别——后者统计的是“前面有多少个不同值”,所以会跳空。
DENSE_RANK() 必须搭配 OVER(ORDER BY ...),否则报错
直接写 DENSE_RANK() 会触发错误:window function requires an ORDER BY clause。它不是标量函数,必须明确排序依据:
-
ORDER BY score DESC是必需的,漏掉就语法报错 - 默认升序(
ASC),高分排最后,容易误判结果——务必显式写DESC - 如果排序字段含
NULL,PostgreSQL 默认NULLS LAST(DESC时在末尾),但不能依赖这个默认;建议显式写ORDER BY score DESC NULLS LAST - 要分组内独立排名?加
PARTITION BY dept_name,但注意:跨组名次不连续(A 组的 2 和 B 组的 2 无关)
WHERE 里不能直接用 DENSE_RANK() 别名
下面这种写法一定报错:
SELECT name, score, DENSE_RANK() OVER (ORDER BY score DESC) AS drk FROM students WHERE drk <= 3;
因为窗口计算发生在 WHERE 过滤之后。正确做法只有两种:
- 用子查询包一层:
SELECT * FROM (SELECT ..., DENSE_RANK() OVER (...) AS drk FROM students) t WHERE drk <= 3 - 用 CTE 更清晰:
WITH ranked AS (SELECT ..., DENSE_RANK() OVER (...) AS drk FROM students) SELECT * FROM ranked WHERE drk <= 3 - 别试图在
GROUP BY后再套DENSE_RANK()——聚合已丢原始行,窗口函数无数据可算
真正容易被忽略的点:并列判定只看 ORDER BY 最左字段
并列与否,完全由 ORDER BY 子句中最左边的字段决定。后面字段只是用于破 ties(打散顺序),不影响是否并列。
错误写法:DENSE_RANK() OVER (ORDER BY score DESC, id ASC) → 即使 score 相同,只要 id 不同,DENSE_RANK() 就可能给出不同值(实际不会,但语义已错)。
正确写法:DENSE_RANK() OVER (ORDER BY score DESC) AS drk,然后在最终 ORDER BY 里补上 score DESC, id ASC 控制展示稳定性。
另外,如果业务要求“Top N 行且严格不多不少”,DENSE_RANK() 不适合——它返回的是“名次 ≤ N”的所有行,可能远超 N 条(比如第 2 名有 15 人并列)。这时候该换 ROW_NUMBER()。

















