row_number() 必须配合 partition by 才能实现分组内独立排序,仅用 order by 会导致全表编号;partition by 指定分组字段,order by 控制组内排序逻辑。

ROW_NUMBER() 必须配合 PARTITION BY 才能分组排序
单独写 ROW_NUMBER() 不会自动按某列分组,它默认对整个结果集编号。要实现“每组内独立排序”,必须显式加上 PARTITION BY 子句——这是最常被忽略的前提。
常见错误是只写 ORDER BY 而漏掉 PARTITION BY,结果得到的是全表序号,不是组内序号。
-
PARTITION BY后跟的字段决定分组依据(如user_id、category),多个字段用逗号分隔 -
ORDER BY在OVER里控制组内排序逻辑,支持多字段和ASC/DESC - 注意:
PARTITION BY的字段必须出现在查询的SELECT或GROUP BY中(取决于上下文),否则可能报错或语义不清
典型写法:给每个用户最近3条订单标序号
场景:查出每个 user_id 下按 created_at 倒序的前3条订单,并标记组内排名。
SELECT user_id, order_id, created_at,
ROW_NUMBER() OVER (
PARTITION BY user_id
ORDER BY created_at DESC
) AS rn
FROM orders
WHERE rn <p>⚠️ 这段 SQL 会报错——<code>rn</code> 是窗口函数结果,不能在 <code>WHERE</code> 中直接引用。正确做法是套一层子查询或 CTE:</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/ai/3864" title="Matrix"><img
src="https://img.php.cn/upload/ai_manual/001/246/273/178599580220637.png" alt="Matrix" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/ai/3864" title="Matrix" class="overflowclass">Matrix</a>
<p class="overflowclass">Matrix是一款AI智能体工具,超长时间自动化运行的主动式多 Agent 协作平台。</p>
</div>
<a rel="nofollow" href="/ai/3864" title="Matrix" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- 用子查询:
SELECT * FROM ( ... ROW_NUMBER() ... ) t WHERE t.rn - 用 CTE 更清晰:
WITH ranked AS (SELECT ..., ROW_NUMBER() OVER (...) AS rn FROM orders) SELECT * FROM ranked WHERE rn - 别把
ORDER BY和PARTITION BY顺序写反,语法要求PARTITION BY在前、ORDER BY在后
和 RANK()、DENSE_RANK() 的关键区别在哪?
三者都生成序号,但处理并列(相同排序值)的方式完全不同,直接影响业务逻辑:
-
ROW_NUMBER():严格递增,哪怕值相同也绝不重复(1,2,3,4) -
RANK():并列时跳号(1,1,3,4) -
DENSE_RANK():并列时不跳号(1,1,2,3)
例如销售额相同时,想“严格按时间先后排唯一名次”,必须用 ROW_NUMBER();若想“同分同名次”,就得换函数。别只看排序效果,先想清楚业务是否允许并列。
性能与索引注意事项
ROW_NUMBER() 是窗口函数,执行时需扫描并暂存分组数据,大数据量下容易成为瓶颈。
- 确保
PARTITION BY字段有索引,尤其当它和ORDER BY字段组合时(如(user_id, created_at)复合索引) - 避免在
SELECT *+ 窗口函数后接大量计算或 JOIN,会放大中间结果集 - MySQL 8.0+、PostgreSQL、SQL Server 都支持,但旧版 MySQL(
真正麻烦的不是写法,而是没想清“分组键是否真的唯一”——比如用 status 分组,但 status 只有 3 个值,却要跑千万级数据,内存和临时表压力会陡增。










