MySQL 8.0+ 应直接使用 ROW_NUMBER() 窗口函数实现每组取前 N 条,需在 OVER() 中指定 PARTITION BY 和 ORDER BY,且必须用子查询或 CTE 包裹才能在外层 WHERE 中过滤序号列。

MySQL 8.0+ 用 ROW_NUMBER() 窗口函数最直接
MySQL 8.0 开始支持窗口函数,ROW_NUMBER() 是解决“每组取前 N 条”最清晰的方式。它按分组和排序生成连续序号,再用外层 WHERE 过滤即可。
常见错误是把 ROW_NUMBER() 放在 WHERE 子句里——窗口函数不能在 WHERE 或 GROUP BY 中使用,必须放在子查询或 CTE 中。
- 先按
GROUP BY字段(如category)分区,再按排序字段(如score DESC)排序 - 外层查询过滤
rn ,注意别漏掉别名引用 - 如果存在并列(相同
score),ROW_NUMBER()仍会强制编号 1/2/3;要保留并列需改用RANK()或DENSE_RANK()
SELECT category, name, score
FROM (
SELECT category, name, score,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY score DESC) AS rn
FROM products
) t
WHERE rn <= 3;PostgreSQL 和 SQL Server 同样适用 ROW_NUMBER()
PostgreSQL 8.4+、SQL Server 2005+ 都支持标准窗口函数语法,上面那段 SQL 几乎可直接复用,只需确认字段名和表名匹配。
差异点在于:SQL Server 对子查询别名要求更严格,必须显式命名(如 t),而 PostgreSQL 允许省略;PostgreSQL 的 ORDER BY 在窗口定义中若不写 NULLS LAST,可能让空值排前面,影响“前 3 条”的实际结果。
- PostgreSQL 中若排序字段含
NULL,建议加NULLS LAST明确语义 - SQL Server 若用旧版(2000/2005 前),只能靠自连接或
TOP+CURSOR,性能差且难维护 - 所有数据库中,
PARTITION BY字段若有索引(尤其联合索引含排序字段),能显著提升开窗性能
MySQL 5.7 及更早版本只能用相关子查询或 JOIN 模拟
没有窗口函数时,本质是为每条记录计算“同组中比它分数高(或相等)的记录数”,再筛选该数量 ≤ 3 的行。相关子查询写法直观但性能堪忧,尤其数据量大时。
典型错误是忽略“并列处理逻辑”:用 < 会漏掉并列第 3 名,用 <= 又可能导致某组返回超过 3 条。必须根据业务需求选比较符。
- 用
<=+COUNT(*)可保留并列,但可能超限;用<则严格保 3 条,但并列第 3 名会被随机截断 - 子查询中
WHERE条件必须精确关联外层,漏写p1.category = p2.category会导致全表笛卡尔积 - 有索引时,
(category, score)联合索引对这类查询帮助极大;否则执行计划常出现Using filesort和Using temporary
SELECT p1.category, p1.name, p1.score FROM products p1 WHERE ( SELECT COUNT(*) FROM products p2 WHERE p2.category = p1.category AND p2.score > p1.score ) < 3;
SQLite 需用 LIMIT 子句配合 UNION ALL(仅适用于少量分组)
SQLite 直到 3.25.0(2018 年)才支持窗口函数,老版本只能对每个分组单独查再合并。这意味着你得知道所有分组值(如全部 category),手工拼 UNION ALL —— 不适合动态分组场景。
容易被忽略的是 LIMIT 在子查询中的行为:SQLite 允许在子查询里用 LIMIT,但 MySQL 5.7 不允许(会报错 This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery')。
- 每组
SELECT必须字段数、类型一致,否则UNION ALL报错 - 若分组太多(比如几百个
category),SQL 长度可能超限,或触发 SQLite 的SQLITE_MAX_COMPOUND_SELECT - 排序必须在每个子查询内完成,外部
ORDER BY只作用于最终结果,无法保证每组内部有序
SELECT * FROM ( SELECT 'A' AS category, name, score FROM products WHERE category = 'A' ORDER BY score DESC LIMIT 3 UNION ALL SELECT 'B', name, score FROM products WHERE category = 'B' ORDER BY score DESC LIMIT 3 );
真正麻烦的不是语法怎么写,而是搞清“前 3 条”到底指什么:是严格 3 条,还是包含并列?有没有时间戳兜底排序?分组键是否允许 NULL?这些业务语义一旦模糊,再漂亮的 SQL 也会返工。

















