row_number() 必须配合 over 中的 order by 使用,否则报错;分页需用子查询或cte先生成行号再过滤,且仅 row_number() 适合分页,rank() 和 dense_rank() 因并列问题会导致跳行或重复。

ROW_NUMBER() 必须配合 ORDER BY 使用,否则报错
直接写 ROW_NUMBER() OVER() 会触发错误:Window function 'ROW_NUMBER' requires an ORDER BY clause。窗口函数需要明确的排序依据才能确定“第几行”,哪怕业务上不关心顺序,也得填一个字段——比如用主键 id 或时间戳 created_at。
常见误操作是套用 ORDER BY 在外层查询里,但窗口函数的排序必须写在 OVER 里面,外层 ORDER BY 不影响行号生成逻辑。
- ✅ 正确:
SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM users - ❌ 错误:
SELECT *, ROW_NUMBER() OVER () AS rn FROM users ORDER BY id(语法报错) - ⚠️ 注意:如果
ORDER BY字段有重复值,ROW_NUMBER()仍会强制分配唯一序号,不会跳过或合并
分页时 WHERE 过滤必须在外层,不能在窗口内
窗口函数在 SQL 执行顺序中早于 WHERE(实际在 FROM→JOIN→WHERE→GROUP BY→HAVING→SELECT→ORDER BY 阶段,SELECT 中的窗口函数在 WHERE 之后、ORDER BY 之前执行),所以你不能靠 ROW_NUMBER() > 10 直接写在 WHERE 子句里——它还没生成出来。
必须用子查询或 CTE 先算出行号,再对外层结果过滤:
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM orders ) t WHERE t.rn BETWEEN 21 AND 40;
- 分页参数建议用变量代替硬编码,例如 PostgreSQL 支持
OFFSET 20 LIMIT 20,但窗口函数方式更可控(尤其需稳定排序时) - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;SQLite 3.25+ 也支持,但旧版不支持窗口函数
- 如果数据量大且
ORDER BY字段无索引,性能会明显下降——务必确保排序字段有索引
区分 ROW_NUMBER()、RANK()、DENSE_RANK() 的分页适用性
分页场景只应使用 ROW_NUMBER()。其他两个函数在遇到并列排序时会生成相同序号,导致跳行或重复行:
-
ROW_NUMBER():严格 1,2,3,4… 每行唯一编号 → ✅ 适合分页 -
RANK():并列时占位,如 1,1,3,4 → ❌ 第 2 页可能漏掉第 2 行 -
DENSE_RANK():并列时不占位,如 1,1,2,3 → ❌ 分页边界不可预测
即使业务排序字段天然去重(如自增 id),也别用 RANK() 替代 ROW_NUMBER()——语义不符,后续加条件易出错。
嵌套窗口或多个排序维度时,ORDER BY 要写全
如果按状态分组再分页,比如“每个 status 内独立编号”,要用 PARTITION BY,此时 ORDER BY 是每个分区内的局部顺序:
SELECT *, ROW_NUMBER() OVER (PARTITION BY status ORDER BY created_at DESC) AS rn FROM tickets;
- 注意:
PARTITION BY status后,ORDER BY created_at DESC只在每个status组内生效 - 如果漏写
ORDER BY,依然报错;如果只写PARTITION BY不写ORDER BY,语法不通过 - 多字段排序要显式声明,比如
ORDER BY status, created_at DESC和PARTITION BY status是不同逻辑,别混淆
真正麻烦的是跨多个业务维度做分页(比如“每个部门每种岗位取最新 3 条”),这时 PARTITION BY + ROW_NUMBER() 是唯一可靠手段,但要注意 ORDER BY 的字段组合是否覆盖所有排序歧义点——少一个字段就可能让同一组内行号不稳定。











