ROW_NUMBER()配合PARTITION BY可为重复值分组编号,核心是按指定字段分组后在组内排序编号;必须指定ORDER BY,推荐用带主键的复合排序确保稳定性;旧版MySQL需谨慎使用变量模拟,优先升级数据库或应用层处理。

用 ROW_NUMBER() 配合 PARTITION BY 处理重复值
直接给重复记录编号,核心是把相同值的行划为一组,再在组内编号。ROW_NUMBER() 是唯一能保证每行拿到不同序号的窗口函数,关键在 PARTITION BY 指定“哪些字段相等才算重复”。比如按 user_id 和 order_date 重复,就写 PARTITION BY user_id, order_date。
注意:ORDER BY 在窗口函数里不可省略,即使只想要任意顺序也得填个字段(如 id 或 created_at),否则报错 Window function requires an ORDER BY clause。
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY user_id, order_date
ORDER BY id
) AS dup_seq
FROM orders;
区分“去重编号”和“全局编号”的场景
如果目标是标记“这是某组内的第几条”,用上面的 PARTITION BY;但如果想给所有重复组本身分配一个唯一 ID(比如把每组重复数据标为 group_1、group_2),就得先去重再编号:
- 用
DISTINCT ON (col1, col2)(PostgreSQL)或GROUP BY(通用)提取唯一组合 - 再套一层
ROW_NUMBER() OVER ()不带PARTITION BY,生成全局递增 ID - 最后和原表
JOIN回去——这步容易漏掉,导致编号无法关联到原始行
MySQL 8.0+ 和 SQL Server 支持 CTE,推荐写成两层 CTE,避免子查询嵌套过深。
ORDER BY 选错字段会导致编号不稳定
ROW_NUMBER() 的排序字段直接影响编号结果。如果用 ORDER BY created_at,但同一组内有毫秒级相同时间,数据库可能按任意物理顺序排,导致多次执行结果不一致。
稳妥做法是补一个确定性字段兜底:
- 加主键
id:如ORDER BY created_at, id - 避免用无索引字段或表达式(如
UPPER(name)),会拖慢性能 - PostgreSQL 中若用
ORDER BY ctid可保证物理顺序,但仅限调试,不可用于生产逻辑
兼容旧版本 MySQL(5.7 及以下)的替代方案
这些版本不支持窗口函数,只能靠变量模拟:@row_number := IF(@prev = CONCAT(col1,col2), @row_number + 1, 1),但风险极高:
- MySQL 不保证变量赋值顺序,尤其在
JOIN或含ORDER BY时极易出错 - 必须严格按
PARTITION BY字段排序,且排序字段要加索引,否则变量值错乱 - 无法在视图或子查询中安全使用,很多 ORM 会自动加包装,直接失效
真要兼容,优先考虑应用层处理,或者升级数据库——变量方案只适合一次性清洗脚本,别放进核心业务 SQL 里。
重复记录编号看着简单,实际最常翻车的是 ORDER BY 字段没兜底、PARTITION BY 漏字段、以及在不支持窗口函数的环境硬上变量。这三个点卡住,后面全白搭。

















