ROW_NUMBER() 是窗口函数,必须配合 OVER() 使用,且 OVER 中需包含 ORDER BY(如 OVER (ORDER BY id)),若需分组则加上 PARTITION BY(如 OVER (PARTITION BY user_id ORDER BY created_at)),二者顺序不可颠倒。

ROW_NUMBER() 必须配合 OVER() 使用,否则直接报错
单独写 ROW_NUMBER() 会触发类似 ERROR: window function ROW_NUMBER requires an OVER clause 的错误。它不是普通函数,而是窗口函数,必须明确指定分区和排序逻辑。
常见错误是只写 OVER() 空括号,这会导致全表视为一个组、按无序排列——结果不可预测,且每次执行可能不同(尤其在无主键/索引的表上)。
- 必须用
ORDER BY子句,哪怕只是按主键排:OVER (ORDER BY id) - 如需分组内编号,加
PARTITION BY,例如按用户分组:OVER (PARTITION BY user_id ORDER BY created_at) -
PARTITION BY和ORDER BY顺序不能颠倒,SQL 标准要求先分组再组内排序
分组后自增编号:PARTITION BY + ORDER BY 缺一不可
比如要给每个 category 内的商品按价格从高到低编号,语句长这样:
SELECT name, category, price, ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS rn FROM products;
这里 PARTITION BY category 定义了“分组边界”,ORDER BY price DESC 决定了组内编号顺序。漏掉任意一个,编号就失去业务意义:
- 没
PARTITION BY→ 全表连续编号,不是“每组从 1 开始” - 没
ORDER BY→ 组内编号随机(依赖底层扫描顺序),不可复现 - 用
RANK()或DENSE_RANK()替代?它们处理并列方式不同,ROW_NUMBER()是严格递增不跳号
WHERE 无法过滤窗口函数结果,得用子查询或 CTE
想只取每组前 3 名?不能直接写 WHERE rn ,因为窗口函数在 <code>WHERE 之后执行。常见错误写法:
SELECT *, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn FROM employees WHERE rn <= 3; -- ❌ 报错:column "rn" does not exist
正确做法是套一层:
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn
FROM employees
) t
WHERE rn <= 3;- CTE 同样有效:
WITH ranked AS (SELECT ..., ROW_NUMBER() ...) SELECT * FROM ranked WHERE rn - 注意别在外部再对
rn做排序——编号已隐含顺序,额外ORDER BY rn可能打乱原分组内的业务序 - 如果表很大,考虑在
PARTITION BY和ORDER BY字段上建联合索引,加速窗口计算
MySQL 8.0+、PostgreSQL、SQL Server 都支持,但旧版 MySQL 不行
如果你用的是 MySQL 5.7 或更早版本,ROW_NUMBER() 直接不可用,会报 FUNCTION xxx.ROW_NUMBER does not exist。替代方案只能靠变量模拟,但极易出错:
SELECT name, category, @row := IF(@prev = category, @row + 1, 1) AS rn, @prev := category FROM products CROSS JOIN (SELECT @row := 0, @prev := '') AS _ ORDER BY category, price DESC;
这种写法问题很多:依赖 ORDER BY 执行顺序(不保证)、并发查询下变量污染、无法在子查询中可靠复用。强烈建议升级到 MySQL 8.0+,或换用标准窗口函数语法。
真正容易被忽略的是:即使数据库支持 ROW_NUMBER(),如果 ORDER BY 表达式存在 NULL 值,默认会把 NULL 排在最前(PostgreSQL)或最后(MySQL 8.0),影响编号起始位置——需要显式写 ORDER BY col DESC NULLS LAST(PostgreSQL)或用 IFNULL() 处理(MySQL)。

















