结论:row_number() 必须配合子查询或cte使用,where无法直接过滤其结果;因窗口函数执行顺序在where之后、order by之前,故where中不可直接引用row_number()。

直接说结论:ROW_NUMBER() 必须配合子查询或 CTE 使用,WHERE 无法直接过滤窗口函数结果——这是最常卡住的地方。
为什么 WHERE 不能直接跟 ROW_NUMBER()
因为 ROW_NUMBER() 是窗口函数,执行顺序在 WHERE 之后、ORDER BY 之前。你写 WHERE ROW_NUMBER() OVER (...) 会报错:<code>invalid use of window function(PostgreSQL/MySQL 8.0+/SQL Server 都一样)。
常见错误写法:
SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS rn FROM products WHERE rn
- 窗口函数只允许出现在
SELECT和ORDER BY子句中 -
WHERE看不到SELECT里刚算出的rn列 - 想“先编号再筛选”,就得把编号这步提前封装好
正确写法:用子查询或 CTE 包一层
把带 ROW_NUMBER() 的查询作为内层,外层再用 WHERE 过滤 rn。
子查询写法(兼容性最好):
SELECT category, name, price
FROM (
SELECT category, name, price,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS rn
FROM products
) t
WHERE t.rn <p>CTE 写法(更易读,推荐):</p><pre class="brush:php;toolbar:false;">WITH ranked AS (
SELECT category, name, price,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS rn
FROM products
)
SELECT category, name, price
FROM ranked
WHERE rn
- 两种方式逻辑完全等价,性能差异通常可忽略
- CTE 更适合多步处理(比如后续还要加
COUNT(*)或连接其他表) - 别名
t或ranked必须有,否则外层无法引用rn
ROW_NUMBER() vs RANK() vs DENSE_RANK():选哪个?
三者都可用于“取前 N 条”,但并列时行为不同:
-
ROW_NUMBER():严格按顺序编号,1,2,3,4… 即使值相同也绝不重复(适合“必须恰好 N 条”) -
RANK():并列时跳号,比如价格相同排第1,则下一个排第3(可能返回少于 N 条) -
DENSE_RANK():并列时不跳号,1,1,2,3…(可能返回多于 N 条)
例如同一 category 下有 4 个产品价格都是最高值:
ROW_NUMBER() → 只取其中任意 3 条(顺序由 <code>ORDER BY后续字段或物理顺序决定)RANK() → 可能一条都不取(如果并列第1,那第2名实际是第5个)DENSE_RANK() → 可能取到 4 条甚至更多(只要排名 ≤3)
性能和索引注意事项
ROW_NUMBER() 的开销主要来自 PARTITION BY 和 ORDER BY 字段的排序。没有索引时,大数据量下可能很慢。
- 确保
PARTITION BY字段(如category)和ORDER BY字段(如price)上有联合索引,例如:CREATE INDEX idx_cat_price ON products(category, price DESC); - 如果只查某几个 category,加
WHERE category IN (...)在子查询外层能显著减少排序数据量 - MySQL 8.0+ 和 PostgreSQL 支持在窗口函数中使用
LIMIT配合OFFSET,但仅限单组;多组仍需ROW_NUMBER()
真正容易被忽略的是:当 ORDER BY 字段存在大量 NULL 值时,不同数据库默认排序行为不一致(有的把 NULL 排最前,有的最后),可能导致每次查询“前 N 条”结果不固定——务必显式写 ORDER BY price DESC NULLS LAST(PostgreSQL)或用 COALESCE(price, 0) 统一处理(MySQL)。










