← 返回题目列表

SQL 反连接是什么?NOT EXISTS、LEFT JOIN IS NULL 和 NOT IN 怎么选?

中等 第 21 / 28 题 更新于 2026/07/29
SQL反连接NOT EXISTSNOT IN

简化版

反连接用于查询“在 A 中存在,但在 B 中不存在”的数据。常见写法有 NOT EXISTSLEFT JOIN ... IS NULLNOT IN。一般推荐优先使用 NOT EXISTS,语义清晰且对 NULL 更安全;LEFT JOIN IS NULL 也常用;NOT IN 遇到子查询结果包含 NULL 时可能返回意外结果。

详细版

例如要查没有下过单的用户,本质就是用户表反连接订单表。NOT EXISTS 会对每个用户判断是否不存在匹配订单;LEFT JOIN IS NULL 先左连接,再筛选右表为空的行;NOT IN 则判断用户 id 不在一个集合里。

三者在很多数据库里可能被优化成相似执行计划,但语义细节不同。最大坑点是 NOT IN 和 NULL:如果子查询集合中出现 NULL,三值逻辑会让比较结果变成 UNKNOWN,最终可能查不出任何行。

面试要能写出 SQL,也要解释为什么 NULL 会影响 NOT IN

完整版教学

一、反连接的业务语义

查没有订单的用户。

查没有绑定角色的账号。

查没有库存记录的商品。

查未完成某步骤的流程实例。

这些都属于“主表中找不到匹配记录”。

二、NOT EXISTS 写法

SELECT u.*
FROM users u
WHERE NOT EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.user_id = u.id
);

这段 SQL 的语义是:不存在任何一条订单属于当前用户。

SELECT 1 只是占位,数据库关注是否存在匹配行。

三、LEFT JOIN IS NULL 写法

SELECT u.*
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.user_id IS NULL;

它先保留所有用户,再把没有匹配订单的用户筛出来。

筛选的列要选择右表中用于匹配且不会无故为 NULL 的列。

四、NOT IN 的 NULL 问题

SELECT *
FROM users
WHERE id NOT IN (SELECT user_id FROM orders);

如果 orders.user_id 中有 NULL,id NOT IN (...) 的判断可能变成 UNKNOWN。

SQL 的 WHERE 只保留 TRUE,不保留 UNKNOWN。

所以结果可能为空或不符合预期。

五、三种写法对比

写法优点风险
NOT EXISTSNULL 安全,语义清晰相关子查询写法稍长
LEFT JOIN IS NULL直观,常见条件放错会变成错误结果
NOT IN简短子查询含 NULL 时危险
EXCEPT集合语义清晰兼容性和去重语义要注意

六、性能和索引

反连接通常需要右表关联列有索引。

例如 orders(user_id)

没有索引时,数据库可能需要大量扫描。

现代优化器常能把 NOT EXISTS 改写为 anti join。

但复杂条件、函数表达式、类型转换会影响优化。

面试里不要只背写法,最好补一句“右表匹配列要建索引,并检查执行计划”。

七、误区和追问

  • 误区:NOT IN 和 NOT EXISTS 永远等价。 子查询含 NULL 时,NOT IN 的结果可能完全不同。
  • 误区:LEFT JOIN 后筛右表任意列 IS NULL 都可以。 应选择能代表匹配是否存在的非空关联列。
  • 误区:反连接一定很慢。 有合适索引和优化器改写时可以很高效。
  • 追问:为什么 NOT IN 遇到 NULL 危险? 因为 SQL 三值逻辑中比较可能得到 UNKNOWN,WHERE 不会保留 UNKNOWN。
  • 追问:查没有订单但用户状态正常怎么写? 用户状态条件放主查询,订单匹配条件放子查询或 ON 中。
  • 追问:EXCEPT 能不能做反连接? 可以表达集合差,但要注意去重、字段一致和数据库支持。

八、面试收束

回答时优先给 NOT EXISTS

再说明 LEFT JOIN IS NULL 的可替代性和 NOT IN 的 NULL 风险。

最后补上索引和执行计划,这样答案比较完整。