必须用子查询包裹 row_number(),再在外部 where 中筛选排名 ≤3 的记录,因窗口函数不能直接在 where 子句中使用。

用 ROW_NUMBER() 排名后取前三,必须套子查询
直接在 WHERE 里写 ROW_NUMBER() OVER (...) 会报错——窗口函数不能出现在 <code>WHERE 子句中,因为执行顺序上,WHERE 在窗口函数计算前就过滤了数据。
正确做法是把带 ROW_NUMBER() 的查询作为子查询(或 CTE),再在外层筛选:rn 。
-
ROW_NUMBER()是最稳妥的选择:它强制唯一编号,即使分数/值相同也不会并列,适合“严格取前 3 条记录”的场景 - 别用
RANK()或DENSE_RANK()直接套WHERE rn ,它们可能返回超过 3 行(比如 3 个并列第 1 名,就占掉全部名额) - 排序字段要明确,尤其多字段排序时:
ORDER BY score DESC, id ASC能避免因排序不稳定导致每次结果不一致
MySQL 8.0+ 和 PostgreSQL 可用 CTE 简化写法
CTE 不是语法糖,它让逻辑更清晰,也方便复用排名结果(比如同时查 Top3 和平均分)。
但注意:SQL Server 和旧版 MySQL(
- PostgreSQL / MySQL 8.0+ 示例:
WITH ranked AS ( SELECT *, ROW_NUMBER() OVER (ORDER BY sales DESC) AS rn FROM orders ) SELECT * FROM ranked WHERE rn
- SQL Server 2012+ 同样支持 CTE,但若用
OFFSET-FETCH替代(见下一条),性能可能更好 - CTE 本身不提升性能,只是组织方式;真正影响快慢的是
ORDER BY字段是否有索引
OFFSET 0 ROWS FETCH FIRST 3 ROWS ONLY 不能替代排名
这个语法只取排序后的前 3 行,不生成“第 1 名、第 2 名”这样的排名值,所以无法用于“找出所有得分等于第 3 名的用户”这类需求。
它适合分页或单纯取头几条,但一旦需要知道“这条记录排第几”,就必须用窗口函数。
- 适用场景:后台列表默认加载前 3 条最新订单,不需要显示“序号”列
- 不适用场景:排行榜页面要标出“你排名第 5”,或需按名次做条件聚合(如“Top3 平均销售额”)
- 兼容性差:SQLite 不支持,MySQL ROWNUM 伪列模拟
排名字段没索引时,ORDER BY 会让查询变慢
窗口函数的 ORDER BY 子句不是可选优化项,它是排名逻辑的基础;如果对应字段没索引,数据库就得对全表排序,数据量一过十万行,延迟就明显了。
特别当排名依据是表达式(如 ABS(price - target))或函数(如 UPPER(name)),普通索引无效,得建函数索引。
- 检查执行计划:关注是否出现
Sort操作节点,以及是否走了索引扫描(Index Scan)而非全表扫描(Seq Scan) - 复合索引有用:比如按
status, created_at DESC排名,建(status, created_at DESC)索引能跳过排序步骤 - 别在大表上对
TEXT或JSON字段直接ORDER BY—— 先提取关键值到生成列,再索引
窗口函数本身不难,难的是记住它和 SQL 执行顺序的冲突点;最容易漏掉的其实是索引——很多人调通了 ROW_NUMBER() 就以为完事了,结果上线后查 10 万行要 3 秒。










