ROW_NUMBER()去重的核心逻辑是:先用PARTITION BY定义分组、ORDER BY确定组内优先级(如updated_at DESC),再通过子查询或CTE外层筛选rn=1,从而保留每组最优行;必须避免漏写PARTITION BY、错用ORDER BY方向、直接WHERE引用窗口函数等常见错误。

ROW_NUMBER() 去重的核心逻辑是什么
ROW_NUMBER() 本身不直接去重,它只是给每行分配一个递增序号;真正实现“分组去重”的关键是配合 PARTITION BY 和 ORDER BY,再用外层查询筛选出每组的第 1 行(即 ROW_NUMBER() = 1)。这个过程本质是“保留每组排序后最优先的一条”,不是无差别删重。
怎么写才能避免常见错误
最容易出错的是 ORDER BY 子句选得不合理,导致保留的不是你想要的那条记录。比如按时间排序却用了 ASC 而你实际需要最新数据,结果留下最老的一条。
- 必须明确指定
PARTITION BY字段——这些字段组合起来定义“重复组”,例如PARTITION BY user_id, product_id -
ORDER BY必须包含能区分优先级的列,如created_at DESC(取最新)、updated_at DESC、或id DESC(取最大主键) - 不能在同一个查询里对窗口函数结果做 WHERE 过滤——必须套一层子查询或 CTE,否则报错
invalid reference to FROM-clause entry - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;但 SQLite 和旧版 MySQL(function ROW_NUMBER does not exist
正确写法示例:
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY email
ORDER BY updated_at DESC, id DESC
) AS rn
FROM users
) t WHERE rn = 1;性能差?可能是这几个地方卡住了
当表很大时,ROW_NUMBER() 去重可能变慢,不是函数本身慢,而是底层排序和分组开销大。
- 确保
PARTITION BY + ORDER BY涉及的字段有联合索引,例如(email, updated_at DESC, id DESC) - 如果只关心“是否存在重复”,别用
ROW_NUMBER(),改用GROUP BY ... HAVING COUNT(*) > 1更快 - 在 PostgreSQL 中,
DISTINCT ON (email) ORDER BY email, updated_at DESC语义等价但通常更快,且语法更简洁 - 不要在大表上直接
UPDATE ... FROM (SELECT ROW_NUMBER() ...)做原地去重——先查出要保留的id列表,再用DELETE WHERE id NOT IN (...),否则容易锁表或 OOM
为什么有时候去重结果还是不对
根本原因常被忽略:NULL 值参与 PARTITION BY 或 ORDER BY 时行为不一致。不同数据库对 NULL 的排序默认处理不同(有的排最前,有的最后),而且 NULL = NULL 为 false,会导致本该同组的记录被拆开。
- 显式处理 NULL:用
COALESCE(email, '___NULL___')或CASE WHEN email IS NULL THEN -1 ELSE email END - 在
ORDER BY中控制 NULL 位置:PostgreSQL 支持NULLS LAST,MySQL 8.0+ 支持ORDER BY col IS NULL, col DESC - 先用
WHERE email IS NOT NULL过滤再计算,比在窗口里硬扛更可控
去重是否生效,最终取决于你如何定义“重复”和“该留哪条”——ROW_NUMBER() 只是工具,逻辑锚点在 PARTITION BY 和 ORDER BY 的组合里。

















