ROW_NUMBER()窗口函数最可靠,因它严格保证每行唯一序号,可稳定按组内任意字段排序编号;RANK()和DENSE_RANK()在并列时会跳号或不跳号,导致返回记录数≠N。

用 ROW_NUMBER() 窗口函数加子查询最可靠
直接在 GROUP BY 后取前 N 条不可行,SQL 标准里分组聚合和“取 Top N”是两类操作,必须用窗口函数或关联子查询。推荐优先用 ROW_NUMBER(),它能稳定按组内排序编号,且支持任意字段组合排序。
常见错误是误用 RANK() 或 DENSE_RANK():当存在并列最大值时,它们会跳号或不跳号,导致实际返回记录数 ≠ N;而 ROW_NUMBER() 严格保证每行唯一序号,配合 WHERE rn 才真正拿到“前 N 条”(哪怕有相同值)。
实操建议:
- 先写内层查询,对目标表按分组字段(如
category)分区,并按排序字段(如score DESC)排序,生成ROW_NUMBER() OVER (PARTITION BY category ORDER BY score DESC) AS rn - 外层查出所有字段,只保留
rn的行,不要在内层加LIMIT或TOP - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;SQLite 3.25+ 也支持,但旧版不支持窗口函数
MySQL 5.7 或更老版本只能用关联子查询
没有窗口函数时,得靠自连接或相关子查询模拟“组内 Top N”。性能较差,数据量大时明显变慢,但兼容性好。
典型写法是:对每条记录,统计同组中比它更大的记录数,若小于 N,则保留该行。例如取每类销量前 3 名:
SELECT t1.* FROM sales t1 WHERE ( SELECT COUNT(*) FROM sales t2 WHERE t2.category = t1.category AND t2.amount > t1.amount ) < 3;
注意点:
- 条件必须用
(不是 <code>),因为“比自己大的数量 <li>如果 <code>amount有重复值,可能返回超过 3 条(比如 4 条并列第 1,都满足“比自己大的数量为 0”);要严格限制条数,需额外加唯一排序字段(如id)打破并列 - 索引很关键:务必在
(category, amount)上建联合索引,否则全表扫描极慢
ORDER BY + LIMIT 在 GROUP BY 后无效,别试
像 SELECT * FROM tbl GROUP BY category ORDER BY score DESC LIMIT 5 这种写法,结果完全不可控——GROUP BY 本身不保证返回哪一行,ORDER BY 和 LIMIT 作用在分组后的聚合结果上,不是组内明细。
有人试图用 GROUP_CONCAT() 拼接再截断,比如:
SELECT category, SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY score DESC), ',', 3) AS top_ids ...
这看似取了前三,但隐患多:
-
GROUP_CONCAT有长度限制(默认 1024 字符),超长会被截断,id或score一长就丢数据 - 返回的是字符串拼接结果,没法直接当 ID 去 JOIN 其他表,还得再拆解
- 无法获取完整行数据(比如除了
id还要name、created_at),只能拼多个字段,更易超长
PostgreSQL 用 LATERAL JOIN 更清晰
如果用 PostgreSQL,LATERAL 是比子查询更直观的替代方案,语义明确:对每组执行一次独立的 Top N 查询。
示例(每类取价格最高的 2 条商品):
SELECT c.category, t.* FROM (SELECT DISTINCT category FROM products) c CROSS JOIN LATERAL ( SELECT * FROM products p WHERE p.category = c.category ORDER BY p.price DESC LIMIT 2 ) t;
优势和注意事项:
- 逻辑分离:外层确定分组键,内层专注单组 Top N,可读性高
- 内层
LIMIT真正生效于每组,不会跨组干扰 - 必须用
CROSS JOIN LATERAL,不能漏掉LATERAL关键字,否则报错或结果错误 - 性能依赖
(category, price)索引,否则每次内层都扫全表
真正难的不是写出来,而是想清楚“前 N 条”是否允许并列、是否需要稳定排序、目标数据库版本是否支持窗口函数——这些决定了你该选哪条路。别为了省事在老 MySQL 上硬套新语法,也别在 PostgreSQL 里还写三层嵌套子查询。

















