DENSE_RANK()最可靠,因其并列不跳号,能准确返回“仅次于最大值”的第二高工资;而ROW_NUMBER()强制编号易错位,RANK()跳号会导致漏取,MAX嵌套或OFFSET LIMIT在并列时亦不可靠。

用窗口函数 ROW_NUMBER() 排序取每组第二行
直接用 GROUP BY 无法拿到“每组第二大”,因为聚合函数(如 MAX())不支持跳过最大值取次大。必须先排序再筛选,ROW_NUMBER() 是最直观可靠的方式。
- 对每组按目标字段降序排序,用
ROW_NUMBER() OVER (PARTITION BY group_col ORDER BY value_col DESC)生成行号 - 外层查询过滤
rn = 2即可,注意:若某组只有 1 条记录,则无第二大数据,该组不会出现在结果中 - 示例:
SELECT group_id, score<br>FROM (<br> SELECT group_id, score,<br> ROW_NUMBER() OVER (PARTITION BY group_id ORDER BY score DESC) AS rn<br> FROM scores<br>) t<br>WHERE rn = 2;
RANK() 和 DENSE_RANK() 的区别在哪
当存在重复值时,ROW_NUMBER() 会强行给相同值不同编号(比如两个并列第一,它标成 1 和 2),导致“第二名”可能被跳过或错位。这时要换用排名函数:
-
RANK():并列名次占用后续位置(1,1,3),所以“第二大”对应RANK() = 2—— 但若最大值有多个,RANK() = 2实际是第三大 -
DENSE_RANK():并列名次不跳号(1,1,2),DENSE_RANK() = 2才真正代表“仅次于最大值的值”,更符合“第二大”的语义 - 推荐优先用
DENSE_RANK(),尤其当业务要求“去重后第二高”时
没有窗口函数的老版本 MySQL 怎么办
MySQL 5.7 或更早不支持窗口函数,得靠自关联或子查询模拟排名,性能差、写法绕,且容易漏数据或重复。
- 典型写法是统计“比当前值大的值有多少个”,例如:
SELECT s1.group_id, s1.score<br>FROM scores s1<br>WHERE (<br> SELECT COUNT(DISTINCT s2.score)<br> FROM scores s2<br> WHERE s2.group_id = s1.group_id AND s2.score > s1.score<br>) = 1;
- 注意:这里用
COUNT(DISTINCT s2.score)是为了处理重复值;若用COUNT(*),重复最大值会导致条件永远不满足 - 性能极差,数据量稍大(>1 万行)就明显变慢,且无法处理某组所有值都相等的情况(此时无
= 1结果)
NULL 值和空组容易被忽略
实际数据里,score 为 NULL 会被 ORDER BY ... DESC 排在最后(取决于 SQL 方言,默认通常排末尾),导致 ROW_NUMBER() 把 NULL 算作一行——如果某组只有 1 个非空值 + 若干 NULL,rn = 2 可能返回 NULL,而非你想要的“第二大非空值”。
- 保险做法是在排序前加
WHERE score IS NOT NULL过滤,或在ORDER BY中显式写ORDER BY score DESC NULLS LAST(PostgreSQL/Oracle 支持,MySQL 不支持) - 空组(即某
group_id在表中无记录)自然不出现在结果里,但若需保留空组占位,得用LEFT JOIN配合主组表,不能只查事实表
真正麻烦的是重复值 + NULL + 少于两行的组混在一起,这时候光看 rn = 2 很容易误判结果含义。

















