EXISTS 是什么
EXISTS 判断相关子查询是否至少返回一行:找到第一行即为真,不必继续找。它表达的是“存在性”,语义上等价于半连接,所以既不放大外层行数,也不会产生重复行——这正是它比 JOIN 加 DISTINCT 更合适的地方。
基本信息
| 语法 | WHERE EXISTS (SELECT 1 FROM t WHERE 关联条件) |
|---|---|
| SELECT 列表 | 内容不参与判断,写 1 是惯例,写 * 结果与性能相同 |
| 相关子查询 | 子查询里引用外层表的列,外层每行判定一次 |
| 语义 | 半连接:命中即真,不放大外层行数、不产生重复 |
| 反向写法 | NOT EXISTS 判断不存在,比 NOT IN 对 NULL 更安全 |
| 常见替代 | IN (子查询)、JOIN + DISTINCT,性能以执行计划为准 |
基础用法
基础用法
SELECT u.id, u.name
FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id -- 关联条件:引用外层的 u.id
AND o.status = 1 -- 条件写在子查询里,不影响外层行数
)
ORDER BY u.id; -- 结果:至少有一条已支付订单的用户NOT EXISTS 与 NOT IN
NOT EXISTS 与 NOT IN
-- 安全:orders 里有 NULL 也不影响结果
SELECT u.id FROM users u
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)
ORDER BY u.id;
-- 有坑:列表里只要有一个 NULL,条件恒为 UNKNOWN,返回 0 行
SELECT u.id FROM users
WHERE u.id NOT IN (SELECT o.user_id FROM orders); -- 一行都查不出来相关写法对比
| 写法 | 语义 | 注意点 |
|---|---|---|
| EXISTS | 半连接:存在即真 | 不放大行数,结果无重复 |
| JOIN + DISTINCT | 连接后去重 | 语义相同但多了去重代价 |
| IN (子查询) | 等于列表中的任一值 | 列表含 NULL 时结果不确定 |
| NOT EXISTS | 反连接:不存在即真 | NULL 安全,反连接首选 |
| NOT IN (子查询) | 不等于列表中的任一值 | 列表含 NULL 时恒不成立 |
典型使用场景
- 筛选有订单、有登录记录、有上报数据的实体
- 反连接:找出从未下单、从未上报、从未登录的对象
- 替代 JOIN 加 DISTINCT,避免先放大行数再去重
- 限定改动范围:只更新与删除存在相关记录的行
- 准入校验类查询:判断某个编码、某个键是否已经存在
- 多条件存在性组合:EXISTS 套 EXISTS 表达“有 A 且没有 B”
相关子查询的代价取决于连接键索引:内表连接列有索引时,外层每行只是几次等值查找;没有索引就退化成外层每行扫一遍内表。判断快慢先看执行计划里的访问方式。
“EXISTS 一定比 IN 快”是过时说法:现代优化器常把 IN 与 EXISTS 都改写成半连接,速度差异更多来自索引。选型应看语义——需要 NULL 安全就选 NOT EXISTS。