row_number()不能在where中直接使用,因为sql执行顺序中where在select之前,而row_number()属于select阶段的窗口函数,必须通过子查询或cte先生成序号再过滤。

ROW_NUMBER() 是 SQL Server 里最常用、也最容易出错的分组取 Top 3 方式。直接写 WHERE ROW_NUMBER() OVER (...) 会报错,必须用子查询或 CTE 包一层——这是绝大多数人卡住的第一步。
为什么不能在 WHERE 中直接用 ROW_NUMBER()
SQL 的逻辑执行顺序中,WHERE 在 SELECT 之前运行,而 ROW_NUMBER() 是在 SELECT 阶段才计算的。所以你写 WHERE rn 时,<code>rn 还根本不存在。
- 错误写法:
SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY score DESC) AS rn FROM t WHERE rn → 报错 “无效的列名” - 正确做法:先算排名,再过滤。CTE 或子查询是唯一合规路径
- SQL Server 2005+ 全支持 CTE,推荐优先用,比多层嵌套子查询更易读
ROW_NUMBER() 的 PARTITION BY 和 ORDER BY 缺一不可
漏掉 PARTITION BY 就等于没分组——你会得到全表连续编号(1,2,3,…),而不是每个分类各自从 1 开始排。
- 错误:只写
ORDER BY score DESC,没PARTITION BY category→ 所有行一起排,不是“每类前三” - 排序字段建议加唯一后备:比如
ORDER BY score DESC, id ASC,避免相同分数时每次结果不一致 - 如果
category字段含NULL,PARTITION BY会把所有NULL归为同一组,需提前WHERE category IS NOT NULL
CTE 写法 vs 子查询:选哪个更稳
两者功能等价,但 CTE 更适合调试和复用;子查询在简单场景下更轻量。真正影响性能的是索引,不是语法形式。
- CTE 推荐写法:
WITH ranked AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY score DESC, id ASC) AS rn FROM products WHERE category IS NOT NULL ) SELECT category, name, score FROM ranked WHERE rn
- 子查询写法:
SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY score DESC) AS rn FROM products) t WHERE t.rn - 别在 CTE 外再套
ORDER BY来“保证顺序”——窗口函数里的ORDER BY已决定编号逻辑,外层ORDER BY只影响最终输出顺序
并列数据怎么处理:ROW_NUMBER() 不是万能解
如果业务上说的“前三名”允许同分并列(比如两个 95 分都算第 1 名),那 ROW_NUMBER() 会强行拆成 1 和 2,可能把真正的第 3 名挤出去。这时候该换 DENSE_RANK()。
-
ROW_NUMBER():严格 3 行,哪怕第 2 名有两个 95 分,也只取其中一个 → 适合“最多三人”场景 -
DENSE_RANK():并列不跳号(95,95,90 → 1,1,2),WHERE dr 能取到所有分数 ≥ 第 3 名的记录 → 更贴近日常语义 - 注意:
RANK()会跳号(95,95,90 → 1,1,3),此时WHERE r 可能漏掉第二个 90 分的记录
真正容易被忽略的点是排序字段的索引。如果 category 基数小(比如只有 5 个分类),但 score 没索引,ROW_NUMBER() 在每组内排序仍会触发大量临时排序操作。上线前务必确认 (category, score) 联合索引是否存在。










