首页/数据/SQL 速查/EXISTS 判断存在性

EXISTS 判断存在性

EXISTS 判断子查询是否有行:逻辑上找到首行即返回真,适合“是否存在”类过滤。

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

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。

易错点

  • 子查询的连接键没有索引:外层每一行都要扫描一遍内表,数据量上来后耗时急剧增长
  • 用 NOT IN 做反连接:子查询结果里出现 NULL 时条件恒不成立,查询返回 0 行却不报错,改成 NOT EXISTS
  • 以为 SELECT 列表影响性能:EXISTS 只看有没有行,SELECT 1 与 SELECT * 等价
  • 用 JOIN + DISTINCT 替代 EXISTS:一对多时先放大行数再全量去重,代价明显更高

数据来源与参考

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