取每组第二条记录最直接的方式是用row_number()按目标字段降序编号后筛选rn=2;若需第二大的不同值,则需先排除组内最大值再编号。

用 ROW_NUMBER() 排序后取每组 rn = 2
最直接、兼容性最好的方式是给每组数据按目标字段降序编号,再筛选编号为 2 的行。注意:必须用 ROW_NUMBER(),不能用 RANK() 或 DENSE_RANK(),否则重复值会跳过编号 2。
-
ROW_NUMBER()保证每行唯一编号,即使值相同也按任意顺序分 1、2、3…,所以能稳定拿到“物理第二条” - 排序字段要明确写在
ORDER BY子句里,比如按score DESC,别漏掉DESC - 窗口函数的
PARTITION BY要和你的“组”逻辑一致,比如按department分组就写PARTITION BY department - 示例:
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY department ORDER BY score DESC) AS rn FROM employees ) t WHERE rn = 2;
遇到重复最大值时,ROW_NUMBER() 和 DENSE_RANK() 结果不同
如果某组内前两名分数都是 95,ROW_NUMBER() 会把其中一条标为 1、另一条标为 2;而 DENSE_RANK() 都标为 1,第三名才标为 2 —— 这时你拿不到“第二大的值”,只拿到“第二大排名的值”。业务上想取“值不等于最大值的最大值”,就得换思路。
- 先算出每组最大值(用
MAX() OVER (PARTITION BY ...)) - 再过滤掉等于最大值的行
- 对剩余行用
ROW_NUMBER()取第一条,即为严格意义上的“第二大的不同值” - 示例:
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY department ORDER BY score DESC) AS rn FROM employees e1 WHERE score
MySQL 8.0+ 可用 LAG(),但仅适用于单列比较场景
LAG() 能获取上一行的值,适合“找出比最大值小、且最接近它的那个值”,但它依赖排序稳定性,且无法直接跨组处理,容易误判。
- 必须配合
PARTITION BY+ORDER BY使用,否则LAG()是全表错位 -
LAG(score)返回的是上一行的score,不是“本组第二大”,如果最大值重复两次,LAG()在第二行返回的仍是最大值 - 真正安全的做法仍是先去重再排序,或用子查询过滤最大值
旧版 MySQL(
核心思路是:对每条记录,统计同组中比它大的记录数,等于 1 的就是第二大。
- 写法冗长,性能差,尤其数据量大时会明显变慢
- 注意关联条件要包含组字段和大小比较,例如:
SELECT e1.* FROM employees e1 WHERE 1 = ( SELECT COUNT(DISTINCT e2.score) FROM employees e2 WHERE e2.department = e1.department AND e2.score > e1.score );
-
COUNT(DISTINCT ...)是关键,避免重复值干扰计数 - 如果允许并列第二(即多个相同第二大值),去掉
DISTINCT;否则保留
实际用的时候,先确认你要的是“第二条记录”还是“第二大的不同值”,再选函数。窗口函数一开,ROW_NUMBER() 基本够用;但只要涉及去重逻辑,就得主动过滤最大值,别指望排序函数自动帮你跳过重复。










