最常用且兼容的in子查询写法是:先在子查询中用group by和having count(*)>1找出重复字段组合,再在外层where中用in匹配原始记录;需注意低版本mysql不支持元组in、null会使in失效,应改用exists或派生表绕过mysql删除限制。

用 IN 子查询定位重复行的主键
最常用也最兼容的写法是:先在子查询里找出重复字段组合及其出现次数,再用外层 WHERE ... IN 拉出所有原始记录。关键在于子查询必须返回能被外层匹配的值(通常是分组字段或主键)。
常见错误是子查询只返回 COUNT(*),结果外层无法 IN 一个数字;或者子查询漏了 HAVING COUNT(*) > 1,导致返回全部数据。
- 要查
users表中email重复的所有行:SELECT * FROM users WHERE email IN (SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1);
- 若按多字段判断重复(如
username和email都相同才算):SELECT * FROM users WHERE (username, email) IN (SELECT username, email FROM users GROUP BY username, email HAVING COUNT(*) > 1);
注意:MySQL 8.0+ 支持元组IN,低版本需改用JOIN或拼接字符串(不推荐) - 如果表没有主键或唯一标识,
IN可能误判——比如两行完全一样,IN会把它们都拉出来,但你其实需要区分“哪条该留、哪条该删”
用 EXISTS 替代 IN 避免空值陷阱
IN 在子查询结果含 NULL 时整个条件恒为 FALSE,查不到任何数据;EXISTS 不受此影响,语义更清晰:只要存在另一行满足条件,就算重复。
适合字段可能为空、或你明确想表达“存在镜像行”逻辑的场景。
- 查
orders表中相同customer_id和order_date的重复订单:SELECT * FROM orders o1 WHERE EXISTS (SELECT 1 FROM orders o2 WHERE o2.customer_id = o1.customer_id AND o2.order_date = o1.order_date AND o2.id o1.id);
-
EXISTS子查询里必须加o2.id o1.id,否则每行都会和自己匹配,全表变“重复” - 性能上,
EXISTS通常比IN更快,尤其子查询结果集大时;但若子查询走不到索引,两者都慢
嵌套查询删除重复数据时的 MySQL 限制
MySQL 不允许直接在 DELETE 语句中对同一张表做子查询修改(报错 You can't specify target table 'X' for update in FROM clause)。必须绕过这个限制。
不是语法写错,而是引擎层面的约束。别硬扛,用标准解法。
- 最稳妥的方式:用派生表(子查询套一层
SELECT)DELETE FROM users WHERE id NOT IN (SELECT min_id FROM (SELECT MIN(id) AS min_id FROM users GROUP BY email) AS t);
- 别写成
DELETE FROM users WHERE id NOT IN (SELECT MIN(id) FROM users GROUP BY email)—— 这会直接报错 - 如果重复量极大,建议先建临时表存去重后的
id,再用JOIN删除,比嵌套查询更稳定
为什么不用窗口函数?它不算嵌套查询
ROW_NUMBER() OVER(...) 是窗口函数,不是嵌套查询。虽然它常和 WITH CTE 一起用,但 CTE 本身只是命名结果集,执行逻辑和嵌套子查询不同。
如果你的 MySQL 版本低于 8.0,根本不能用窗口函数;即使能用,它也不属于“嵌套查询”范畴,不符合当前问题的技术边界。
真正容易被忽略的是:嵌套查询的执行顺序——子查询先跑完,结果固化后再跑外层。这意味着子查询里不能引用外层的列(除非相关子查询),也不能依赖外层的计算过程。一旦混淆,结果就不可控。











