row_number() 必须配合 partition by 和 order by 才能实现分组取 top n;单独使用不加 partition by 仅为全表编号,无法按组取前 n。

ROW_NUMBER() 必须配合 PARTITION BY 和 ORDER BY 才能分组取 Top N
单独写 ROW_NUMBER() 不加 PARTITION BY 就是全表编号,根本不是“每组取前 N”。关键在于:先用 PARTITION BY 划分逻辑组(比如按 category 或 user_id),再用 ORDER BY amount DESC 在每组内排序,最后用外层查询过滤 rn 。
常见错误是把 ORDER BY 写在最外层——没用,窗口函数的排序必须在 OVER() 里定义。
-
PARTITION BY字段必须和业务分组维度一致,比如查“每个部门工资最高的 3 人”,就得写PARTITION BY dept_name - 如果金额相同,
ROW_NUMBER()会强制分配不同序号(比如并列第 1 会变成 1 和 2),需要并列处理得换RANK()或DENSE_RANK() - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;SQLite 3.25+ 也行,但旧版不支持窗口函数
实际写法:子查询 + ROW_NUMBER() 过滤最简可靠
不能直接在 WHERE 里用 ROW_NUMBER(),因为窗口函数不可在 WHERE 中引用。必须套一层子查询或 CTE。
SELECT user_id, category, amount
FROM (
SELECT user_id, category, amount,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY amount DESC) AS rn
FROM orders
) t
WHERE t.rn <p>这个结构兼容性最好,所有支持窗口函数的数据库都能跑。注意别漏掉别名 <code>t</code>,否则外部 <code>WHERE</code> 会报错“列不存在”。</p>
- 别在
ORDER BY里混用多个字段又不加优先级说明,比如ORDER BY amount DESC, created_at DESC才能保证金额相同时按时间保序 - 如果表很大,记得给
PARTITION BY和ORDER BY涉及的字段建联合索引,例如(category, amount) - 别用
SELECT *套子查询——窗口函数列(如rn)会暴露给上层,可能干扰应用逻辑
和 RANK() / DENSE_RANK() 的区别不能只看文档,得看业务需求
金额并列时行为完全不同:ROW_NUMBER() 强行连续编号(1,2,2,4),RANK() 跳号(1,2,2,4),DENSE_RANK() 不跳号(1,2,2,3)。选哪个取决于你要不要“挤占名额”。
例如“每个品类销量前 3 名”,若两个商品并列第 1,你希望总共返回 4 条(含并列者),就得用 RANK();若严格只要 3 条,哪怕牺牲并列也要截断,才用 ROW_NUMBER()。
-
ROW_NUMBER()结果一定唯一,适合做分页或取固定条数 -
RANK()更贴近“排行榜语义”,但WHERE rn 可能返回超过 3 行 - 没有“自动去重”逻辑,如果原始数据本身有重复行,窗口函数照常编号,需提前用
DISTINCT或GROUP BY处理
WHERE 条件要写在子查询外层,且不能下推到窗口内
想筛出“每个活跃用户最近 3 笔订单”,不能把 status = 'paid' 放在窗口子查询里再过滤 rn——那样会先取所有订单编号,再筛状态,结果错误。必须先过滤再编号。
SELECT user_id, order_id, amount
FROM (
SELECT user_id, order_id, amount,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders
WHERE status = 'paid' -- ✅ 过滤必须放这里
) t
WHERE t.rn <p>顺序错了就会导致:比如某用户有 10 笔订单,其中只有 2 笔是 paid,但如果你把 <code>WHERE</code> 放外层,<code>ROW_NUMBER()</code> 已对全部 10 笔编号,<code>rn 可能选出 unpaid 记录。</code></p>
- 复杂条件(如日期范围、多状态)一律前置到子查询的
WHERE,不是靠外层AND补救 - 如果过滤字段不在
PARTITION BY或ORDER BY中,不影响窗口计算,但会影响输入行数,务必确认逻辑先后 - 某些 ORM(如 Django ORM)生成的子查询可能隐式包裹多层,容易误判 WHERE 位置,建议先看生成 SQL 再调
实际用的时候,最容易被忽略的是过滤时机和并列语义——前者导致数据错,后者导致条数不对。别急着套模板,先想清楚“并列算不算前 N”和“我要筛的是原始数据还是编号后数据”。










