ROW_NUMBER() 必须配合 OVER 子句使用,且不能直接在 WHERE 子句中引用,因其为窗口函数,执行顺序晚于 WHERE,需通过子查询或 CTE 间接过滤。

ROW_NUMBER() 必须配合 OVER 子句,且不能直接在 WHERE 中引用
很多人写 WHERE ROW_NUMBER() OVER (...) ,结果报错——因为窗口函数不能出现在 <code>WHERE 或 HAVING 中。正确做法是先用子查询或 CTE 把编号算出来,再过滤。
典型错误现象:ERROR: window functions are not allowed in WHERE clause
- 必须把
ROW_NUMBER()放在子查询/CTE 的SELECT列表里 -
OVER中的PARTITION BY定义分组依据(比如按category分),ORDER BY决定每组内排序逻辑(比如按sales DESC) - 别在
ORDER BY里用没出现在SELECT中的列,某些数据库(如 SQL Server)会报错
CTE 是最清晰、可读性最强的写法
用 WITH 先生成带序号的结果集,再外层筛选,逻辑一目了然,也方便调试中间结果。
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM employees
)
SELECT employee_id, name, department, salary
FROM ranked
WHERE rn <= 3;
- 如果只要 Top 1,把
rn 换成 <code>rn = 1更高效(避免排序多余行) - 注意:不同数据库对 CTE 的支持略有差异,MySQL 8.0+、PostgreSQL、SQL Server 都支持;旧版 MySQL 需用派生表替代
- 如果分组字段有
NULL,PARTITION BY department会把所有NULL归为同一组——这通常是预期行为,但需确认业务是否允许
用子查询替代 CTE 时要注意括号和别名
不是所有环境都支持 CTE(比如某些 ORM 自动生成语句或老版本数据库),这时得用嵌套子查询,但容易漏掉必需的别名。
常见错误:忘记给子查询起别名,导致 FROM (SELECT ...) WHERE ... 报语法错误。
- 子查询必须有别名,哪怕只是
AS t - 示例:
SELECT employee_id, name, department, salary FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn FROM employees ) AS t WHERE rn <= 3; - 别名
t后面不能跟.引用字段(如t.rn)——因为WHERE在同一层级,直接用rn即可
ROW_NUMBER() 和 RANK()/DENSE_RANK() 的区别直接影响“Top N”的语义
如果组内有并列数据(比如两人同为最高薪),ROW_NUMBER() 会强制分出 1、2、3;而 RANK() 给并列者相同名次,跳过后续名次(1、1、3);DENSE_RANK() 也是并列,但不跳(1、1、2)。
- 要严格取前 N 行(不管是否并列),用
ROW_NUMBER() - 要取“实际排名 ≤ N”的所有记录(允许重复、可能超 N 行),改用
RANK()或DENSE_RANK() - 例如:想取每个部门薪资排名前 3 的人,但若第 3 名有两人并列,则
ROW_NUMBER()只返回其中一人,RANK()会返回两人(共 4 行)
真正容易被忽略的是:业务上“Top 3”到底指“最多 3 条”,还是“所有排名 ≤ 3 的人”。这个语义差异必须和产品/业务方对齐,不能只看 SQL 能不能跑通。

















