首页/数据/SQL 速查/NOT EXISTS 反连接

NOT EXISTS 反连接

NOT EXISTS 找“没有对应匹配”的行:与 NULL 无关,是反连接的推荐写法;配合内表索引效率稳定。

SQL 速查数据更新于 2026-10-13

NOT EXISTS 是什么

NOT EXISTS 用来找出「没有对应匹配」的行,是反连接的推荐写法。它只关心子查询是否存在结果,存在即返回真、不存在返回假,整个过程与 NULL 无关,因此没有 NOT IN 那种遇到 NULL 就整体返回空的陷阱。使用时把子查询写成关联子查询(内表引用外表的列),并在内表连接键上建索引,执行效率通常与左连接加 IS NULL 相当甚至更好。

基本信息

作用找出在另一张表里找不到匹配的行
语义子查询有结果为真,无结果为假,与 NULL 无关
写法NOT EXISTS (SELECT 1 FROM 内表 WHERE 内表键 = 外键)
内表别名子查询里的 SELECT 1 只是占位,写什么列都不影响结果
性能前提内表连接列上要有索引
适用平台标准写法,MySQL 8.0 与 PostgreSQL 都支持

典型使用场景

  • 找未下单用户:用户表里不存在对应的订单记录
  • 找未处理工单:没有对应的处理记录
  • 数据一致性核对:源表有而目标表没有的行
  • 清理前置查询:先查出候选再决定是否删除
  • 打标:把从未登录过的账号标记为休眠
  • 迁移校验:核对两个库里只在一边存在的记录

示例

示例
-- 找出没有订单的用户
SELECT u.id, u.name
FROM users u
WHERE NOT EXISTS (
    SELECT 1 FROM orders o
    WHERE o.user_id = u.id        # 关联条件:内表引用外表列
      AND o.status = 'paid'       # 附加条件写在子查询里
);

-- 对比:NOT IN 在内层列可空时会返回空结果
SELECT u.id FROM users u
WHERE u.id NOT IN (SELECT user_id FROM orders WHERE user_id IS NOT NULL);

反连接写法对照

写法NULL 行为性能提示
NOT EXISTS不受影响内表连接键需有索引
NOT IN内层含 NULL 时结果为空内层列可空时不可用
LEFT JOIN + IS NULL不受影响判断列选内表主键
EXCEPT / MINUS集合运算不受影响PostgreSQL 支持,可读性好
子查询里的 SELECT 1 只是占位,列清单不影响结果;把连接与过滤条件都写进子查询,可读性最好。
关联条件写错会得到错误结果:内表必须引用外表的列,漏掉这一条就变成对整个内表的判断,结果往往全为真或全为假。
内表连接列没有索引时 NOT EXISTS 会退化为对外表每一行扫一次内表,数据量大时极慢,改写后务必用 EXPLAIN 确认。

使用要点

  • 优先用 NOT EXISTS 表达反连接,语义不受 NULL 影响
  • 附加过滤条件写进子查询,不要把内表条件写到外层 WHERE
  • 确认内表连接列有索引,必要时先补索引
  • 改写后用 EXPLAIN 对比,确认访问方式变好
  • 结果集用于删除时先做行数核对与备份
  • 团队规范里把 NOT IN 加子查询列为重点复查写法

易错点

  • 关联条件漏写内表与外表的对应关系,子查询变成常量判断,结果全错
  • 内表连接列没有索引,NOT EXISTS 退化为逐行全表扫描
  • 把内表过滤条件写在外层 WHERE,把未匹配行过滤掉,结果与预期相反
  • 改写后没有对比行数,改前改后结果不同却按新结果执行删除
  • 在该用 NOT IN 的简单常量场景里也用 NOT EXISTS,SQL 变复杂且没有收益

数据来源与参考

  • MySQL 8.0 Reference Manual · Subqueries · NOT EXISTS
  • PostgreSQL Documentation · EXISTS
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌