MySQL中DELETE子查询删重需加派生表别名,如DELETE FROM users WHERE id NOT IN (SELECT * FROM (SELECT MIN(id) FROM users GROUP BY email) AS tmp);PostgreSQL须用USING自连接,如DELETE FROM users u1 USING users u2 WHERE u1.email = u2.email AND u1.id > u2.id。

DELETE 语句里用子查询删重复数据,必须加别名
MySQL 和 PostgreSQL 中直接写 DELETE FROM t WHERE id IN (SELECT id FROM t GROUP BY col HAVING COUNT(*) > 1) 会报错:「You can't specify target table 't' for update in FROM clause」。根本原因是 MySQL 禁止在同一个语句中对同一张表既查又删。
解决办法是给子查询套一层派生表(即加别名),让优化器认为这是“另一张表”:
DELETE FROM users
WHERE id NOT IN (
SELECT * FROM (
SELECT MIN(id)
FROM users
GROUP BY email
) AS tmp
);
注意最内层的 SELECT MIN(id) 是保留每组重复记录中最小 id 的那条;外层 SELECT * 是为了给派生表提供一个合法列名(否则 MySQL 8.0+ 可能报错);AS tmp 这个别名不可省略。
PostgreSQL 要用 USING + 自连接,不能套 IN 子查询
PostgreSQL 不支持 DELETE ... WHERE col IN (SELECT ...) 对自身表的引用(即使加别名也不行),会提示「table name "users" specified more than once」。
正确做法是用 USING 关键字做自连接,配合窗口函数或聚合判断重复:
DELETE FROM users u1 USING users u2 WHERE u1.email = u2.email AND u1.id > u2.id;
这个语句删掉所有“存在相同 email 且 id 更大”的记录,等效于每组只留 id 最小的那条。实际执行前建议先用 SELECT 验证:
SELECT u1.* FROM users u1 JOIN users u2 ON u1.email = u2.email AND u1.id > u2.id;
WHERE 条件漏掉 GROUP BY 字段,可能误删整组
常见错误是只按单个字段去重,却忽略业务上真正决定“重复”的组合条件。比如用户表中 name 和 phone 联合才算重复,但写了 GROUP BY name,结果把不同手机号同名的人全删了。
务必核对去重依据是否完整:
- 检查业务定义:是单字段唯一?还是多字段联合唯一?
- 子查询里的
GROUP BY必须和业务规则一致,例如GROUP BY name, phone - 如果用
ROW_NUMBER() OVER (PARTITION BY ...),PARTITION BY也要对应相同字段
另外,HAVING COUNT(*) > 1 只能用于找重复组,不能直接用于 DELETE 的 WHERE——它只能出现在子查询里,且必须嵌套在派生表中。
没加 LIMIT 或事务控制,删库跑路风险极高
DELETE 没带 LIMIT 且没包在事务里,一旦条件写错就是不可逆删除。尤其子查询逻辑稍复杂时,很容易多删或少删。
安全操作三步走:
- 先用
SELECT模拟要删的数据,例如:SELECT id, email FROM users WHERE id NOT IN (SELECT * FROM (SELECT MIN(id) FROM users GROUP BY email) AS t) - 确认条数合理后,再执行
DELETE,首次运行建议加LIMIT 100控制范围 - 生产环境必须在
BEGIN; ... DELETE ... ; COMMIT;中执行,方便出错回滚
特别注意:某些 ORM(如 Django ORM)生成的子查询可能自动加别名,但原生 SQL 手写时,MySQL 的派生表别名、PostgreSQL 的自连接别名,一个都不能少——少一个就执行失败或删错数据。










