安全地去除重复行
删除完全重复和近似重复的行,同时保留一条规范记录
安全地去除重复行 是 CoddyKit 上的免费 Coding Interview Prep 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Coding Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Coding Interview Prep 课程共包含 4 节课。
去重问题
“这张表中有重复行。请删除重复行,但每行只保留一份。”几乎每场数据工程面试都会以某种形式考查这个问题。挑战在于要安全地完成操作:恰好保留一行规范记录,同时不能误删那些只是看起来相似、实际却不同的记录。
我们将讲解如何检测重复行、选择要保留的副本、在 SELECT 中进行去重,以及从表中实际删除重复行。
先定义重复行
首先要问面试官的问题是:“什么情况下两行算作重复行?”常见定义包括:
- 完全重复:每一列都相同。
- 键重复:业务键相同(例如
email相同),但其他列可能不同。
针对不同定义,所用技术也不同。请不要自行假设;澄清重复行的定义是最重要的一步,面试官也希望您主动询问。
检测重复行
要查找重复键,请按定义重复关系的列进行分组,并保留计数大于一的分组。这样可以在进行任何修改前,了解哪些键受到影响以及每个键存在多少个副本。
先运行检测查询是一项值得明确说出的最佳实践:在删除之前,您可以先确认问题的规模。
SELECT email, COUNT(*) AS copies
FROM users
GROUP BY email
HAVING COUNT(*) > 1
ORDER BY copies DESC;完全重复:DISTINCT
如果重复行确实在每一列上都完全相同,那么只读的去重视图可以简单地写成 SELECT DISTINCT *。不带 ALL 的 UNION 也会删除重复行。
但只有在您希望对整行去重、且不需要选择要保留的副本时,DISTINCT 才有帮助。对于列值不同的基于键的重复行,您需要使用排名。
-- Read-only dedup of exact-duplicate rows
SELECT DISTINCT customer_id, name, signup_date
FROM customers;键重复:ROW_NUMBER
当多行共享同一个键、但其他列不同时,请按键进行分区,并为每个副本编号。rn = 1 标记要保留的行;rn > 1 标记要丢弃的多余行。
窗口中的 ORDER BY 决定要保留哪个副本作为规范副本。请有意识地进行选择,例如保留最近更新的行。
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY email
ORDER BY updated_at DESC
) AS rn
FROM users;选择规范副本
请将编号逻辑封装在 CTE 中,只保留 rn = 1。这样每个键会返回一行,具体就是您的 ORDER BY 排名第一的那一行。
这种 SELECT 写法不会破坏数据:它非常适合构建干净的视图,或者通过 INSERT ... SELECT 将数据写入去重后的目标表,而不会修改源表。
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY email ORDER BY updated_at DESC
) AS rn
FROM users
)
SELECT user_id, email, name, updated_at
FROM ranked
WHERE rn = 1;排序选择很重要
分区中的 ORDER BY 是业务决策,而不是形式要求:
ORDER BY updated_at DESC会保留最新的记录。ORDER BY created_at ASC会保留最初创建的记录。ORDER BY id ASC会保留数值最小的代理键,适合用作稳定的任意选择。
请加入唯一的并列决胜字段,以便在主要排序列也出现并列时,仍能确定地选出目标行。
ROW_NUMBER() OVER (
PARTITION BY email
ORDER BY updated_at DESC, id ASC
) AS rn实际删除重复行
要真正从表中删除重复行,请先识别多余行(rn > 1),然后将其删除。在 PostgreSQL 和 SQL Server 中,您可以使用 CTE 进行删除;在 MySQL 中,自连接或子查询很常见。
请始终先运行匹配的 SELECT,预览究竟哪些行将被删除。盲目删除正是候选人在这道题上失分的原因。
WITH ranked AS (
SELECT ctid,
ROW_NUMBER() OVER (
PARTITION BY email ORDER BY updated_at DESC, id ASC
) AS rn
FROM users
)
DELETE FROM users
WHERE ctid IN (SELECT ctid FROM ranked WHERE rn > 1);自连接删除模式
一种经典且通用的方法是保留每个重复键下 id 最小的行,并使用自连接删除其余行。它不需要窗口函数,这在较旧的数据库引擎上很有价值。
连接条件会将每一行与另一行配对,后者拥有相同的键但更小的 id;任何存在这样一个较小 id 对应行的行,都是需要删除的重复行。
DELETE u1
FROM users u1
JOIN users u2
ON u1.email = u2.email
AND u1.id > u2.id;安全检查清单
删除之前,请采取以下保护措施:
- 将删除操作放在事务中,这样如果数量看起来不对,就可以执行
ROLLBACK。 - 先运行要删除行的
SELECT COUNT(*),并检查结果是否合理。 - 请考虑创建备份表:
CREATE TABLE users_bak AS SELECT * FROM users。 - 确认您的
PARTITION BY列确实定义了重复关系,否则可能会误删不同的记录。
BEGIN;
-- run the DELETE, inspect row count
-- COMMIT; if correct, otherwise ROLLBACK;近似重复与规范化
有时多行并不完全相同,但在逻辑上却表示同一项:'Ann@X.com' 与 'ann@x.com',或者存在尾随空格。请基于规范化后的表达式进行分区,而不是直接使用原始列。
提到规范化能够体现您的实践经验:现实中的重复项经常隐藏在大小写、空白字符或格式差异之后,简单的键比较会漏掉它们。
ROW_NUMBER() OVER (
PARTITION BY LOWER(TRIM(email))
ORDER BY updated_at DESC, id ASC
) AS rn快速检查
请选择安全的去重方法。
回顾:安全去重
请按步骤进行去重:
- 首先定义什么是重复行,然后使用 GROUP BY / HAVING COUNT(*) > 1 进行检测。
- 完全重复 →
DISTINCT。键重复 → 按键进行分区的ROW_NUMBER,保留rn = 1。 - 窗口中的
ORDER BY决定规范副本;请加入唯一的并列决胜字段。 - 预览计数后,在事务中删除
rn > 1的行。 - 请规范化键,以捕获近似重复项。
常见问题解答
「安全地去除重复行」课时是免费的吗?
是的 — 「安全地去除重复行」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Coding Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 Coding Interview Prep 课程共包含 4 节课。
「安全地去除重复行」这节课中我会学到什么?
删除完全重复和近似重复的行,同时保留一条规范记录 你通过在浏览器中直接运行的动手代码来练习 Coding Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Coding Interview Prep 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Coding Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「安全地去除重复行」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Coding Interview Prep 课中编写并运行代码吗?
能。每节 Coding Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。