用 dense_rank() 是因为其对重复值赋予相同排名且后续排名连续,如[100,100,95,90]→[1,1,2,3],能准确获取逻辑上的第二大值;而 row_number() 会强制编号为[1,2,3,4],导致并列第一时“第二名”被错误覆盖。

为什么用 DENSE_RANK() 而不是 ROW_NUMBER()
当字段存在重复最大值时,ROW_NUMBER() 会把并列第一的两条记录编为 1 和 2,导致“第二名”被错误覆盖;而 DENSE_RANK() 对相同值赋予相同排名,且后续排名连续,比如 [100, 100, 95, 90] → 排名是 [1, 1, 2, 3],真正拿到的是逻辑上的第二大值。
常见错误现象:ROW_NUMBER() 在有重复最高值时返回空或错值,尤其在分组场景下极易漏数据。
- 适用场景:需要保留并列排名语义(如“并列第一之后就是第二名”,不跳过)
- 不适用场景:要严格取“排序后第2个物理位置”的记录(此时才该用
ROW_NUMBER()) - 性能影响:三者窗口函数开销接近,但
DENSE_RANK()更少依赖ORDER BY的唯一性,索引利用更稳定
DENSE_RANK() 分组取第二大值的标准写法
核心结构是两层查询:内层加排名,外层过滤 dr = 2。注意必须带 PARTITION BY 才算“每个分组”。
示例(查每个学生第二高分的科目):
SELECT student, subject, score
FROM (
SELECT student, subject, score,
DENSE_RANK() OVER (PARTITION BY student ORDER BY score DESC) AS dr
FROM test
) AS ranked
WHERE dr = 2;
-
PARTITION BY student是关键,漏掉就变成全表排,不是“每个学生” -
ORDER BY score DESC必须明确降序,否则dr = 2拿到的是倒数第二 - 如果某学生只有一门课,
dr = 2查询结果中不会出现该学生——这是预期行为,不是 bug
遇到 NULL 或全相同值时怎么处理
DENSE_RANK() 会把 NULL 当作最小值(默认排序下排最后),若字段全为同一值(如全是 85),则所有记录 dr = 1,dr = 2 查不到结果。
- 想排除
NULL再排名:在内层WHERE score IS NOT NULL - 想让全相同值也返回(哪怕“第二”和“第一”一样):不行——
DENSE_RANK()定义上没有“第二”,只有“次高排名”。此时应改用MAX()嵌套排除法 - 业务上需兜底显示“无第二名”:用
LEFT JOIN或窗口聚合补缺,不能靠DENSE_RANK()自动产出
索引怎么建才真正提速
仅对排序字段建单列索引(如 score)效果有限;DENSE_RANK() 在分组场景下,最优索引是复合索引,顺序必须匹配 PARTITION BY + ORDER BY。
- 正确写法:
CREATE INDEX idx_student_score ON test (student, score DESC); - 错误写法:
CREATE INDEX idx_score_student ON test (score DESC, student);—— 无法支持分组内快速定位 - 如果表已有主键或聚簇索引含
student,再加score列可形成隐式覆盖,不一定非要新建索引
没索引时,10 万行以上数据可能触发临时表 + 文件排序,执行时间从毫秒级升至秒级——这个性能断层最容易被忽略。










