面试sql重复数据问题需分清查重、去重、防重、修复四场景:查重用group by+having或窗口函数;临时去重用distinct或row_number();永久删除用子查询保留min(id);预防靠unique约束或唯一索引,应用层校验须兜底。

面试中遇到SQL重复数据问题,核心是分清场景:是查重、去重、防止重复,还是修复已有重复。关键不在写多炫的语句,而在理解业务意图和数据一致性要求。
一、怎么快速查出表里有哪些重复数据?
先定位问题,再处理。用GROUP BY + HAVING是最直接的方式,重点看重复依据字段(比如姓名+手机号组合):
- 查出所有重复的“姓名+邮箱”组合,并统计次数:
SELECT name, email, COUNT(*) FROM users GROUP BY name, email HAVING COUNT(*) > 1; - 想看到重复行的完整记录(含ID),可用窗口函数(MySQL 8.0+/PostgreSQL/SQL Server支持):
SELECT * FROM (SELECT *, COUNT(*) OVER (PARTITION BY name, email) AS cnt FROM users) t WHERE cnt > 1;
二、临时去重:只查不改,返回无重复的结果集
业务查询需要“去重后展示”,但不能删数据——这时不用DELETE,优先考虑逻辑去重:
-
DISTINCT:适用于整行完全相同(所有字段都一样):
SELECT DISTINCT name, email FROM users; -
ROW_NUMBER():按某规则取每组第一条(如保留最新注册的用户):
SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY name, email ORDER BY id DESC) rn FROM users) t WHERE rn = 1; - 注意:GROUP BY也能“聚合去重”,但必须明确非分组字段的聚合逻辑(如MAX(id)),否则报错或结果不可控。
三、永久去重:删除已有重复,只留一条
操作前务必备份!真实删除要谨慎,推荐用子查询或CTE锚定保留行:
- MySQL常用自连删除(保留最小id):
DELETE u1 FROM users u1 INNER JOIN users u2 WHERE u1.name = u2.name AND u1.email = u2.email AND u1.id > u2.id; - 通用安全写法(推荐):先查出要删的ID,再删:
DELETE FROM users WHERE id NOT IN (SELECT MIN(id) FROM users GROUP BY name, email);
(注意:若GROUP BY字段含NULL,需额外处理,因NULL != NULL)
四、预防重复:唯一约束 vs 唯一索引 vs 应用层校验
面试常考设计权衡。根本解法是数据库层面加固:
- UNIQUE约束:建表时加,或后期ALTER,失败时抛异常(如“Duplicate entry”),开发需捕获并提示用户;
- 唯一索引:效果同UNIQUE约束,但可包含NULL(多数引擎允许多个NULL),且支持联合、前缀等高级用法;
- 应用层校验(如先SELECT再INSERT)有竞态风险,高并发下可能仍插入重复——必须配合数据库约束兜底;
- 扩展思考:若业务允许“软重复”(如同一人多个账号),但需标记主账号,则用外键+标志位,而非强行唯一。
不复杂但容易忽略:NULL值在唯一性判断中的特殊行为、字符集排序规则对比较的影响、以及批量导入时约束是否启用(SET FOREIGN_KEY_CHECKS=0等)。真正考察的是你有没有踩过坑、想过边界。










