ROW_NUMBER() 是窗口函数,必须配合 OVER() 使用,否则报错;OVER() 中需指定 ORDER BY(不可省略),可选 PARTITION BY 分组,编号严格连续且唯一确定。

ROW_NUMBER() 必须配合 OVER() 使用,否则报错
直接写 ROW_NUMBER() 会触发语法错误,比如 MySQL 8.0+ 或 PostgreSQL 报 function ROW_NUMBER() does not exist,SQL Server 报 Incorrect syntax near ')' 。它不是普通标量函数,而是窗口函数,必须带 OVER() 子句。
常见写法是:ROW_NUMBER() OVER (ORDER BY column_name) —— 这表示按某列全局排序后编号;如果要分组编号,就得在 OVER() 里加 PARTITION BY。
-
PARTITION BY定义分组维度(如按category或user_id分),每组内独立编号 -
ORDER BY在每个分区内指定排序依据,决定编号顺序,**不可省略**(否则报错) - 多个字段可一起分区或排序,例如:
OVER (PARTITION BY dept_id ORDER BY salary DESC)
分组序号从 1 开始,且严格连续,无法跳过或重置
ROW_NUMBER() 的行为很“死板”:只要 PARTITION BY 和 ORDER BY 确定,结果就唯一确定,不会因重复值、NULL 或过滤条件而中断计数。
比如同一组内两条记录 score 相同,ORDER BY score 下谁排前谁排后是不确定的(除非加二级排序,如 ORDER BY score, id),但编号一定是 1、2,不会出现 1、1 或 1、3。
- WHERE 条件在窗口函数计算之后生效,所以先编号再过滤 → 编号可能不连续(如只取
rn ,但原组有 5 行) - 想实现“每组取最新一条”,常用
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY create_time DESC)+WHERE rn = 1 - 不要指望用
ROW_NUMBER()实现类似 Excel 中“手动编号”或“跳过空行”的逻辑,它只响应 SQL 执行计划中的行序
不同数据库对 NULL 的排序处理不一致
ORDER BY 中若字段含 NULL,MySQL 默认把 NULL 排最前,PostgreSQL 默认排最后,SQL Server 取决于 SET ANSI_NULLS 设置。这直接影响 ROW_NUMBER() 的编号起点。
例如:ROW_NUMBER() OVER (PARTITION BY region ORDER BY updated_at),如果某组内 updated_at 全为 NULL,那它们会被集中排在一起,编号就是 1、2、3……而不是“没顺序就不编号”。
- 显式控制 NULL 位置:用
ORDER BY updated_at ASC NULLS FIRST(PostgreSQL/Oracle 支持),但 MySQL 不支持NULLS FIRST语法 - MySQL 替代方案:
ORDER BY ISNULL(updated_at), updated_at(把 NULL 当 0 排最前)或ORDER BY IFNULL(updated_at, '1970-01-01') - SQL Server 中可用
ORDER BY updated_at ASC OFFSET 0 ROWS之类绕过,但最稳仍是补默认值或提前清洗
性能敏感场景下避免在大表上无过滤地开窗
ROW_NUMBER() 需要扫描并排序整个分区数据,如果 PARTITION BY 维度太粗(比如全表只有一个分区),或分区数据量极大(单个 user_id 有百万级记录),就会显著拖慢查询,甚至 OOM。
- 加索引能加速:组合索引应覆盖
PARTITION BY列 +ORDER BY列,例如(region, updated_at) - 别在 SELECT * 基础上直接套窗口函数,先用子查询或 CTE 过滤出必要字段和行
- 某些场景可用变量模拟(MySQL 5.7 及以前):
@rn := IF(@prev = user_id, @rn + 1, 1),但结果不稳定,不推荐用于生产 - 物化中间结果(如建临时表存分组 TOP-N)比反复计算更可靠,尤其涉及多次引用同一编号结果时
实际用的时候,最容易被忽略的是 ORDER BY 的强制性——很多人卡在“为什么报错”,其实就差这一句;其次是误以为编号会随 WHERE 自动重排,结果发现 rn = 1 拿到的不是预期那条。

















