except是sql中用于返回第一个查询结果中存在但第二个查询结果中不存在的非重复行的集合差运算符,要求两个select语句列数相同、对应列数据类型兼容且顺序一致,默认自动去重,不修改原表数据。

EXCEPT 不能直接删除数据,它只返回差异结果集
很多人误以为 EXCEPT 是个修改操作,其实它和 SELECT 一样,纯属查询语句。执行 SELECT * FROM t1 EXCEPT SELECT * FROM t2 只会输出在 t1 中存在、但不在 t2 中的行,不会动原表一丁点数据。
想“删除差异”,必须把 EXCEPT 的结果作为子查询或 CTE,再套进 DELETE。而且要注意:SQL 标准里 EXCEPT 要求左右两边列数、类型、顺序完全一致,否则报错 column count does not match 或类型不兼容。
- 确保两个
SELECT返回相同数量、顺序、兼容类型的列(比如都选id, name,别一边选name, id) - 如果表结构不同,先用显式列名投影对齐,不要依赖
* -
EXCEPT自动去重,若需保留重复行,得改用NOT EXISTS或左连接
用 DELETE + EXCEPT 的典型写法(PostgreSQL / SQL Server)
主流支持 EXCEPT 的数据库(如 PostgreSQL、SQL Server)允许在 DELETE 的 WHERE ... IN 或 USING 子句中嵌套它。MySQL 不支持,得绕开。
例如:从 orders_new 中删掉那些在 orders_old 里没有对应 order_id 和 status 组合的记录:
DELETE FROM orders_new WHERE (order_id, status) IN ( SELECT order_id, status FROM orders_new EXCEPT SELECT order_id, status FROM orders_old );
- 括号里的
(order_id, status)是行构造器,要求两边SELECT列完全一致 - PostgreSQL 支持这种行值比较;SQL Server 需改用
EXISTS+ 左连接模拟 - SQLite 支持
EXCEPT,但不支持行值 in 子查询,得拆成两个独立条件
MySQL 用户必须换方案:NOT EXISTS 或 LEFT JOIN
MySQL 直到 8.0.32 才实验性支持 EXCEPT,且不支持在 DML 中嵌套。硬要用,会报错 ERROR 1235 (42000): This version of MySQL doesn't yet support 'EXCEPT'。
安全可靠的替代是 NOT EXISTS:
DELETE FROM orders_new
WHERE NOT EXISTS (
SELECT 1 FROM orders_old
WHERE orders_old.order_id = orders_new.order_id
AND orders_old.status = orders_new.status
);
- 性能通常比
LEFT JOIN更好,尤其当orders_old有复合索引(order_id, status) - 注意字段是否允许 NULL:如果
status可为 NULL,NOT EXISTS仍能正确处理,而!=或LEFT JOIN ... IS NULL容易漏掉 NULL 匹配 - 务必给关联字段建索引,否则全表扫描会让 DELETE 极慢
真正删数据前,永远先用 SELECT 验证 EXCEPT 结果
差异逻辑一旦写错,DELETE 就不可逆。最稳妥的做法是:把准备删的语句,先改成 SELECT 执行一遍,看返回的行是不是你预期要删的。
比如原 DELETE 是:
DELETE FROM t1 WHERE id IN (SELECT id FROM t1 EXCEPT SELECT id FROM t2);
就先跑:
SELECT id FROM t1 EXCEPT SELECT id FROM t2;
- 检查返回的
id是否真的属于“该删的”——比如是否混入了测试数据、软删除标记未过滤等 - 确认
t2数据是最新快照,不是某天导出的旧备份 - 线上操作建议加
LIMIT 10先试删几条,观察日志和监控
EXCEPT 的语义很干净,但它的“干净”建立在严格列对齐和明确业务意图上;少一个字段映射,或多一个隐式类型转换,结果就可能完全偏离预期。











