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 加子查询列为重点复查写法