EXCEPT和MINUS不能直接互换,因语法受数据库方言严格限制:EXCEPT用于SQL Server、PostgreSQL等,MINUS专属于Oracle;二者语义一致,均返回左查询有而右查询无的去重行,要求列数、类型、顺序严格匹配。

EXCEPT 和 MINUS 到底能不能互换用?
不能直接互换,语法和语义都受数据库方言严格限制。EXCEPT 是标准 SQL(SQL:2003 起)关键字,被 PostgreSQL、SQL Server、SQLite 支持;MINUS 是 Oracle 专属,MySQL 完全不支持二者。PostgreSQL 里写 MINUS 会报错 syntax error at or near "MINUS",Oracle 里写 EXCEPT 同样报错。
它们行为一致:返回左查询结果中存在、但右查询结果中**完全不存在**的行(自动去重,且要求列数、数据类型、顺序严格匹配)。
- 列名以左查询为准,右查询列名被忽略
- NULL 值参与比较时,
(1, NULL)和(1, NULL)被视为相同行(多数引擎如此,但注意 Oracle 对 NULL 的处理在某些旧版本有细微差异) - 不保证结果排序,如需有序,必须显式加
ORDER BY
为什么 SELECT * FROM A EXCEPT SELECT * FROM B 返回空?
常见于表结构看似一致但实际存在隐性不匹配。不是数据问题,而是类型或精度对不上。
-
CHAR(10)和VARCHAR(10)在部分引擎(如 SQL Server)中比较时可能因尾部空格处理不同导致整行不等价 - 数值型字段:左表是
DECIMAL(5,2),右表是FLOAT,即使值显示一样(如3.14),二进制表示不同,EXCEPT视为不同行 - 时间字段:
TIMESTAMP WITHOUT TIME ZONEvsTIMESTAMP WITH TIME ZONE,即使值相同,类型不兼容直接报错而非静默跳过 - 别名干扰:如果用了子查询且未显式指定列别名,而两方列名不一致(如左为
id,右为user_id),部分引擎(如 older PostgreSQL)会因列名不匹配拒绝执行
想保留重复行怎么办?EXCEPT 不行,该用什么?
EXCEPT 和 MINUS 都强制去重,无法保留左表中的重复记录。例如 A 表有 3 行 (1,'a'),B 表有 1 行 (1,'a'),结果只返回 0 行,而不是 2 行。
要实现“差集并保留重复”,得绕开集合操作符,改用 LEFT JOIN + IS NULL 或 NOT EXISTS:
SELECT a.* FROM table_a a LEFT JOIN table_b b ON a.id = b.id AND a.name = b.name WHERE b.id IS NULL;
- 这个写法不会去重,A 中每条未被 B 匹配上的记录都会原样输出
- 注意
ON条件必须覆盖所有用于判断“相等”的列,漏一个就可能产生错误匹配 - 性能上,若 B 表很大,确保
(id, name)有联合索引,否则LEFT JOIN可能比EXCEPT慢得多
MySQL 用户别硬凑:没有 EXCEPT/MINUS 怎么办?
MySQL 直到 8.0.31 才通过窗口函数间接模拟,但没原生支持。强行用 UNION ALL + COUNT 太重,也不可靠。
- 最稳妥是用
NOT EXISTS(推荐):SELECT * FROM table_a a WHERE NOT EXISTS ( SELECT 1 FROM table_b b WHERE b.id = a.id AND b.name = a.name );
- 避免用
NOT IN:如果table_b.id有NULL,整个查询结果为空——这是 MySQL(和其他多数引擎)的语义陷阱 - 如果只是临时查一次且数据量小,导出两表 CSV 用命令行工具差分(如
comm -23 )反而更快
复杂点在于:差集不是简单“删掉右边有的一行”,而是基于行级全字段精确匹配。很多人卡在类型隐式转换、NULL 处理、或误以为它像编程语言里的 set difference 那样可配置比较逻辑——它不可配置,只能接受引擎定义的相等规则。

















