SQL中如何获取每个分组中字段值排名前三的记录?

使用 ROW_NUMBER() 配合 PARTITION BY 可实现分组 Top 3:先按分组维度分区、组内按指定字段降序排序并编号,再在外层筛选 rn ≤ 3 的记录。

sql中如何获取每个分组中字段值排名前三的记录?

ROW_NUMBER() 配合 PARTITION BY 实现分组 Top 3

直接上最常用也最可控的解法:窗口函数 ROW_NUMBER()。它按分组内排序生成连续整数,不会跳过重复值,适合“取前 3 条”这种明确数量的需求。

关键点在于:PARTITION BY 定义分组维度,ORDER BY 决定组内排序依据(比如按销售额降序),再用外层查询过滤 rn 。

  • 示例(MySQL 8.0+ / PostgreSQL / SQL Server / Oracle):
SELECT *
FROM (
  SELECT *,
         ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales DESC) AS rn
  FROM orders
) t
WHERE rn <= 3;
  • category 是分组字段,sales 是排序字段,可替换成你实际的列名
  • 注意:如果同一组内有多个记录 sales 相同,ROW_NUMBER() 会强制给它们不同排名(比如 1、2、3、4),可能把本该并列第 3 的记录挤出去
  • 若需保留并列情况(如两个第 2 名后直接第 4 名),改用 RANK()DENSE_RANK(),但要小心结果条数可能超过 3

RANK()DENSE_RANK() 的区别在哪?什么时候该换用?

当业务要求“并列也算名额”时,ROW_NUMBER() 就不合适了。比如某组销售前三名是 100、95、95、90,你希望取出 4 条记录(两个第 2 名 + 第 3 名),就得选 RANK()

  • RANK():并列时跳过后续名次(1,2,2,4)→ 可能返回 >3 行
  • DENSE_RANK():并列不跳号(1,2,2,3)→ 更贴近“实际名次感”,但同样可能超 3 行
  • 如果必须严格限制为最多 3 行,且允许并列,则只能用 RANK() + 外层加额外逻辑(比如取 rank 后再按主键去重或截断)
  • MySQL 5.7 或更早版本不支持窗口函数,得用自关联或变量模拟,性能差、易出错,建议升级或换方案

旧版 MySQL(

没有 ROW_NUMBER() 时,常见做法是用相关子查询数“当前记录在组内排第几”,但数据量一大就明显变慢。

SELECT o1.*
FROM orders o1
WHERE (
  SELECT COUNT(*)
  FROM orders o2
  WHERE o2.category = o1.category
    AND o2.sales > o1.sales
) < 3;
  • 核心逻辑:统计同组中比当前记录 sales 更高的数量,小于 3 即说明是 Top 3
  • 注意:这里用的是 而不是 <code>,因为 COUNT 从 0 开始计数(最高者 COUNT=0)
  • 性能隐患极大:每行都触发一次子查询,10 万行数据可能变成千万级扫描
  • 如果表有复合索引 (category, sales),能显著加速,否则慎用

GROUP BY 不能直接配合 LIMIT 实现分组 Top N

有人试过写 GROUP BY category LIMIT 3,发现只返回 3 条总记录,不是每组 3 条——这是常见误解。

  • GROUP BY 是聚合操作,LIMIT 作用于最终结果集,不是每个分组
  • 试图用 GROUP_CONCAT() 拼接再解析,既难维护又无法保留原始字段结构
  • 真正需要“每组 Top N”时,窗口函数是唯一干净解法;没窗口函数就只能接受性能妥协或应用层处理

真正麻烦的不是语法,而是想清楚“并列怎么算”和“数据量是否扛得住子查询”。这两点没理清,再漂亮的 SQL 也可能上线就卡住。

文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/jiquanzatan/126659.html

iQOO手机怎么开启充电特效开关自定义电量颜色
上一篇 2026-07-19 13:13
vivo手机怎么开启隐私保护下的后台应用卡片内容模糊显示
下一篇 2026-07-19 13:13

相关推荐