相关 EXISTS 与 NOT EXISTS
掌握能够正确处理 NULL 的稳健反连接替代方案
相关 EXISTS 与 NOT EXISTS 是 CoddyKit 上的免费 SQL Interview Prep 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 SQL Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 SQL Interview Prep 课程共包含 4 节课。
EXISTS 用于测试是否存在
EXISTS 接受一个子查询,只要该子查询返回至少一行,就会返回 TRUE;否则返回 FALSE。它永远不会返回这些行本身。
当内部包含相关子查询时,EXISTS 就会变成针对每条外层行的存在性测试:“是否存在与当前外层行匹配的行?”
由于遇到第一条匹配行就会提前停止,它并不关心匹配了多少行。这个语义细节是面试中很受欢迎的考点。
基本的相关 EXISTS
找出至少下过一个订单的客户。内层查询通过 o.customer_id = c.customer_id 建立相关性。
对于每位客户,EXISTS 都会询问:是否存在该客户的任何订单?如果存在,就保留该客户。
SELECT c.customer_id, c.name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
);为什么在 EXISTS 中使用 SELECT 1
您会在 EXISTS 中看到 SELECT 1、SELECT * 或 SELECT NULL。它们完全等价。
EXISTS 只检查是否有行返回,从不检查行的内容,因此投影列并不重要。优化器会忽略这些列。
SELECT 1 是一种常见约定,用来表达意图:“我只关心是否存在。”请选择一种写法并保持一致,不要让面试官误以为这里的列列表很重要。
NOT EXISTS 查找缺失项
NOT EXISTS 会反转测试:只有当相关子查询返回零行时,才保留外层行。
这就是标准的反连接:没有订单的客户、从未售出的产品、没有提交记录的学生。
SELECT c.customer_id, c.name
FROM customers c
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
);NOT IN 的 NULL 陷阱
这是面试中的重点。针对可能包含 NULL 的子查询使用 NOT IN 时,行为十分危险:只要列表中有一个 NULL,NOT IN 就会完全不返回任何行。
这是因为与 NULL 比较会得到 UNKNOWN,而 NOT IN 要求每一次比较都为假。一个 UNKNOWN 就会使整个条件失效。
NOT EXISTS 不会受到这个问题影响;它只检查行是否存在,并且能够安全处理 NULL。
-- Risky: returns nothing if any o.customer_id is NULL
SELECT c.customer_id FROM customers c
WHERE c.customer_id NOT IN (SELECT o.customer_id FROM orders o);
-- Safe: NULLs do not break it
SELECT c.customer_id FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);为什么 NOT EXISTS 可以安全处理 NULL
原因在于匹配逻辑。NOT EXISTS 会检查是否存在满足 o.customer_id = c.customer_id 的内层行。
当 o.customer_id 为 NULL 时,该行永远不会满足这个等式(NULL = 任意值的结果是 UNKNOWN,而不是 TRUE),因此它不会被算作匹配项。存在性测试仍然是正确的。
使用 NOT IN 时,同一个 NULL 会成为列表比较的一部分,其 UNKNOWN 结果会清除所有输出。这就是高级职位面试更偏好 NOT EXISTS 的原因。
带有额外条件的 EXISTS
相关子查询可以携带更多谓词。请找出至少下过一笔金额超过 1000 的订单的客户。
额外条件位于 EXISTS 子查询内部,并针对每个客户限定其作用域。
SELECT c.customer_id, c.name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
AND o.amount > 1000
);性能:短路行为
EXISTS 在找到一行匹配项后即可停止扫描内层关系。它不会构建或统计完整的结果集。
因此,EXISTS 通常效率较高,尤其是在相关列已建立索引时,因为每次逐行探测都能快速找到匹配项并提前退出。
相比之下,相关的 COUNT(*) > 0 会强制统计所有匹配项。如果您只需要是或否的答案,请优先使用 EXISTS。
EXISTS 与 COUNT 的存在性比较
候选人有时会编写相关计数来测试是否存在。这样虽然可行,但会浪费计算量。
COUNT 版本会统计每一笔匹配的订单,而 EXISTS 在找到第一笔后就会退出。对于纯粹的存在性测试,EXISTS 能表达意图,并允许优化器执行短路。
-- Works but counts everything
SELECT c.customer_id FROM customers c
WHERE (SELECT COUNT(*) FROM orders o
WHERE o.customer_id = c.customer_id) > 0;
-- Better: stops at first match
SELECT c.customer_id FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id);示例:从未下过订单的产品
这是一个经典的反连接面试题:列出从未被下过订单的产品。NOT EXISTS 几乎就是英文要求的直译。
对每个产品检查是否有任何订单明细行引用它;只保留没有订单明细行引用的产品。
SELECT p.product_id, p.name
FROM products p
WHERE NOT EXISTS (
SELECT 1
FROM order_items oi
WHERE oi.product_id = p.product_id
);在 NOT EXISTS 中使用 EXISTS 编写除法类查询
将 EXISTS 嵌套在 NOT EXISTS 中可以表达关系除法:“查找与一个集合中的 ALL 都匹配的行。” 一个经典问题是“找出购买了某个类别中每种产品的客户”。
其逻辑是:当不存在任何一个客户未购买的产品时,保留该客户。这个双重否定是除法查询的典型特征,面试官会借此测试您对 EXISTS 的深入掌握程度。
SELECT c.customer_id
FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM products p
WHERE p.category = 'Coffee'
AND NOT EXISTS (
SELECT 1 FROM order_items oi
JOIN orders o ON o.order_id = oi.order_id
WHERE oi.product_id = p.product_id
AND o.customer_id = c.customer_id
)
);快速检查
请选择查找没有订单的客户的最安全方式。
回顾:相关 EXISTS 和 NOT EXISTS
要点:
EXISTS是逐行执行的存在性测试,在找到第一项匹配后就会短路;其中选择哪一列并不重要(使用SELECT 1)。NOT EXISTS是 NULL 安全的反连接,用于查找没有匹配项的行。- 如果列表中包含 NULL,
NOT IN会返回任何结果都没有;请优先使用NOT EXISTS。 - 对于存在性判断,EXISTS 优于相关的
COUNT(*) > 0,因为它会提前停止。
请主动提及 NOT IN 的 NULL 陷阱;这是体现结构化查询语言成熟度的可靠信号。
常见问题解答
「相关 EXISTS 与 NOT EXISTS」课时是免费的吗?
是的 — 「相关 EXISTS 与 NOT EXISTS」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 SQL Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 SQL Interview Prep 课程共包含 4 节课。
「相关 EXISTS 与 NOT EXISTS」这节课中我会学到什么?
掌握能够正确处理 NULL 的稳健反连接替代方案 你通过在浏览器中直接运行的动手代码来练习 SQL Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 SQL Interview Prep 需要有经验吗?
无需任何先前经验。CoddyKit 上的 SQL Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「相关 EXISTS 与 NOT EXISTS」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 SQL Interview Prep 课中编写并运行代码吗?
能。每节 SQL Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 相关子查询的结构
- 不使用 GROUP BY 的分组汇总
- 相关 EXISTS 与 NOT EXISTS
- 将相关子查询重写为连接