窗口函数去重是什么
给同一去重键的行按“保留优先级”排序编号,再只取编号为 1 的行,是“每组只留最新或最优先一条”的通用解法。相比 GROUP BY,它能保留整行的所有列,不受聚合列限制;相比相关子查询,它通常只扫一遍表。
基本信息
| 核心写法 | ROW_NUMBER() OVER (PARTITION BY 去重键 ORDER BY 优先级) AS rn,外层取 rn = 1 |
|---|---|
| 保留整行 | 先编号再筛行,明细列全都能带上 |
| 排序优先级 | 业务时间加主键,保证同值行也有确定顺序 |
| 与 GROUP BY 的区别 | GROUP BY 每组一行但只能出聚合列,窗口去重能保留明细列 |
| 去重键选择 | 邮箱、手机号、业务编号,需要先与业务确认口径 |
| 删除重复 | MySQL 可用自连接或子查询句柄配合 DELETE 批量删除 |
每组只留最新一行
每组只留最新一行
WITH ranked AS (
SELECT u.*,
ROW_NUMBER() OVER (PARTITION BY u.email -- 去重键
ORDER BY u.updated_at DESC, -- 优先级:新的优先
u.id DESC) -- 次级键,保证顺序确定
AS rn
FROM users u
)
SELECT id, email, updated_at
FROM ranked
WHERE rn = 1; -- 每个邮箱只留一行GROUP BY 做不到的取整行
GROUP BY 做不到的取整行
-- 只想要最大更新时间:GROUP BY 足够
SELECT email, MAX(updated_at) FROM users GROUP BY email;
-- 想同时取出那条记录的其它列:GROUP BY 表达不了,必须用窗口去重
SELECT id, email, updated_at
FROM (
SELECT u.*,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY updated_at DESC, id DESC) AS rn
FROM users u
) t
WHERE rn = 1; -- Oracle/MySQL 8/PostgreSQL 都支持这种写法去重写法对比
| 写法 | 输出 | 适用场景 |
|---|---|---|
| GROUP BY | 每组一行、仅聚合列 | 只需要汇总值 |
| ROW_NUMBER 取 1 | 每组一行、可保留全部列 | 取整行,最常用 |
| RANK / DENSE_RANK | 并列时可能每组多行 | 要求并列都保留 |
| 相关子查询 | 每组一行、逐行判定 | 数据量小、逻辑直观 |
| DISTINCT | 全列完全相同的行去重 | 不需要优先级排序 |
典型使用场景
- 用户表按邮箱去重,保留最后更新的那条记录
- 订单按业务编号去重,保留状态最靠后的一条
- 多来源合并后按业务键去重,保留优先级最高的来源
- 日志表取每个会话的首条与末条记录
- 清理重复数据前先选出待删清单,核对无误再执行删除
- 同步任务按幂等键只取一行,避免向下游重复写入
先编号再过滤与先过滤再编号结果不同:先 WHERE 再编号,被过滤掉的行就不参与优先级判断。按业务决定顺序——要把它们排除在选择之外,还是只排除在展示之外。
排序键不足以唯一确定时“最新”是随机的:写入时间精度相同、状态并列都可能让同一组每次取到不同行。排序里补一列主键,结果才可复现。