except可直接排除关联结果,但要求左右查询列数、数据类型、顺序严格一致;它自动去重,将null视为相等,需显式过滤null以避免误剔。

EXCEPT 能直接排除关联结果,但必须字段完全一致
EXCEPT 是集合运算符,它从左查询结果中剔除与右查询结果**完全相同的所有行**。关键点在于:左右两个 SELECT 的列数、数据类型、顺序必须严格一致,否则报错 ORA-01789: 查询块具有不正确的结果列数 或 column data type mismatch。
常见误用场景是想排除“用户已关注的博主”,却写成:
SELECT id, name FROM users EXCEPT SELECT user_id, followee_id FROM follows;
这会失败——users.id 对应 follows.user_id 没问题,但 users.name 和 follows.followee_id 类型/语义不匹配。正确写法要对齐字段:
- 如果目标是查“没被任何用户关注过的博主”,应统一查
id字段:
SELECT id FROM users EXCEPT SELECT followee_id FROM follows;
-
EXCEPT自动去重,如需保留重复行,得用EXCEPT ALL(PostgreSQL 支持,MySQL 不支持) - SQL Server 用
EXCEPT,Oracle 用MINUS,MySQL 8.0+ 才支持EXCEPT;低版本 MySQL 只能靠LEFT JOIN模拟
LEFT JOIN + IS NULL 是最通用的排除写法
当需要排除“存在关联但不满足某条件”的记录时,LEFT JOIN 配合 WHERE ... IS NULL 更灵活可靠。它不依赖字段类型对齐,还能嵌套条件。
比如查“注册满30天但从未发过帖的用户”:
SELECT u.id, u.email FROM users u LEFT JOIN posts p ON u.id = p.user_id AND p.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY) WHERE p.id IS NULL;
注意这里 AND p.created_at >= ... 写在 ON 子句里,而不是 WHERE——否则会把所有无帖子的用户都过滤掉,失去“注册满30天”的限定意义。
- 把过滤条件放在
ON中:只影响右表匹配逻辑,保留左表全部记录 - 把过滤条件放在
WHERE中:先完成 JOIN,再整体过滤,可能意外筛掉左表行 - 索引建议:确保
posts(user_id, created_at)有联合索引,否则LEFT JOIN性能会急剧下降
NOT EXISTS 比 LEFT JOIN 更清晰表达“不存在”语义
当排除逻辑本质是“左表某行在右表中找不到匹配项”,NOT EXISTS 的意图比 LEFT JOIN ... IS NULL 更直白,且通常执行计划更优(尤其右表数据量大时)。
查“没有未读消息的用户”:
SELECT id, name FROM users u WHERE NOT EXISTS ( SELECT 1 FROM messages m WHERE m.user_id = u.id AND m.is_read = 0 );
-
NOT EXISTS子查询只要找到一行就短路退出,而LEFT JOIN会尝试匹配所有右表行 - 子查询里必须关联外层表(如
m.user_id = u.id),否则变成NOT EXISTS (SELECT 1 FROM ...),永远为假 - MySQL 5.7+、PostgreSQL、SQL Server 都支持,语法一致;SQLite 支持但子查询不能含
LIMIT
EXCEPT 和 NOT EXISTS 在 NULL 处理上表现不同
这是最容易被忽略的坑:EXCEPT 把 NULL 当作相等值处理(即 NULL = NULL 成立),而 NOT EXISTS 和 LEFT JOIN 遵循 SQL 标准的三值逻辑(NULL = NULL 结果为 UNKNOWN,不满足 TRUE)。
假设 follows.followee_id 允许为 NULL,执行:
SELECT id FROM users EXCEPT SELECT followee_id FROM follows;
结果中不会包含 followee_id IS NULL 的行——因为 EXCEPT 认为 NULL 和任意 NULL 相等,会被当作“已存在”剔除。但业务上,NULL followee_id 往往表示脏数据或未初始化,不该参与排除逻辑。
此时应显式过滤:
SELECT id FROM users EXCEPT SELECT followee_id FROM follows WHERE followee_id IS NOT NULL;
或者换用 NOT EXISTS,天然避开 NULL 匹配歧义。










