使用连接模拟集合运算
在不支持 EXCEPT 和 INTERSECT 的方言中重写它们
使用连接模拟集合运算 是 CoddyKit 上的免费 Coding Interview Prep 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Coding Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Coding Interview Prep 课程共包含 4 节课。
为什么要模拟集合运算
并非每个数据库都支持 INTERSECT 和 EXCEPT。例如,较旧版本的 MySQL 完全不支持它们。面试官会考查您是否能在运算符不可用时,使用连接和子查询重现集合逻辑。
同时了解集合运算符及其等价的连接写法,能够证明您理解该运算符实际计算的内容。
将 INTERSECT 实现为 INNER JOIN
INTERSECT 会找出两个集合共有的行。等价的连接写法是在所有待比较列上使用 INNER JOIN,并加上 DISTINCT 以匹配去重行为。
比较中的每一列都会成为连接谓词的一部分。
-- A INTERSECT B emulated:
SELECT DISTINCT a.customer_id
FROM orders_2023 a
JOIN orders_2024 b
ON a.customer_id = b.customer_id;为什么 INTERSECT 需要 DISTINCT
普通的 INNER JOIN 可能会产生扇出:如果某个值在任一侧出现多次,连接会使行数成倍增加。标准 INTERSECT 会使每个共同的行只返回一次,因此需要添加 DISTINCT 来合并连接引入的重复行。
忘记在这里使用 DISTINCT 是面试中的常见失误。
-- without DISTINCT, a customer with 3 orders in each year
-- would appear 9 times from the joinEXCEPT 的 LEFT JOIN / IS NULL 写法
EXCEPT(A 中有而 B 中没有)就是反连接。可移植的写法是:从 A 到 B 按所有列执行 LEFT JOIN,只保留 B 侧为 NULL(没有匹配)的行,然后使用 DISTINCT。
这种 LEFT JOIN / IS NULL 模式是 SQL 面试中最常复用的技巧之一。
SELECT DISTINCT a.customer_id
FROM orders_2023 a
LEFT JOIN orders_2024 b
ON a.customer_id = b.customer_id
WHERE b.customer_id IS NULL;EXCEPT 与 NOT EXISTS
同样可移植的 EXCEPT 写法是使用 NOT EXISTS。它的含义是“保留每一行 A,前提是不存在匹配的 B 行”,并且能够稳健地处理 NULL。
许多工程师更喜欢 NOT EXISTS,因为它的意图很明确,也能避开 NOT IN + NULL 陷阱。
SELECT DISTINCT a.customer_id
FROM orders_2023 a
WHERE NOT EXISTS (
SELECT 1 FROM orders_2024 b
WHERE b.customer_id = a.customer_id
);INTERSECT 与 EXISTS
同理,INTERSECT 可以使用 EXISTS 来写:保留每一行不重复的 A,前提是存在匹配的 B 行。
EXISTS 会在首次匹配时提前结束,因此可能更高效,也能避免连接扇出,有时还可以不必在连接侧使用 DISTINCT。
SELECT DISTINCT a.customer_id
FROM orders_2023 a
WHERE EXISTS (
SELECT 1 FROM orders_2024 b
WHERE b.customer_id = a.customer_id
);NOT IN 的 NULL 陷阱
一种看似合理的 EXCEPT 模拟方式是 NOT IN,但它很危险:如果子查询返回任意 NULL 值,NOT IN 将完全不返回任何行,因为比较结果会变成 UNKNOWN。
这是一个经常被测试的易错点。请优先使用 NOT EXISTS 或 LEFT JOIN / IS NULL,它们能够安全处理 NULL。
-- RISKY if orders_2024.customer_id can be NULL:
SELECT DISTINCT customer_id FROM orders_2023
WHERE customer_id NOT IN (
SELECT customer_id FROM orders_2024
);在多个列上进行匹配
当集合比较涉及多个列时,每一列都必须参与连接谓词。对于反连接,您还必须处理这些列中可能存在 NULL 的情况,这正是 NOT EXISTS 的优势所在。
请在 ON 子句中明确写出每一列;漏掉一列会悄然改变“相等行”的定义。
SELECT DISTINCT a.id, a.city
FROM a
LEFT JOIN b
ON a.id = b.id AND a.city = b.city
WHERE b.id IS NULL;不使用运算符模拟 UNION
UNION ALL 就是拼接,每种方言都直接支持它。要在需要时模拟去重后的 UNION,可在子查询中使用 UNION ALL 进行拼接,再用 SELECT DISTINCT 包裹,或对所有列执行 GROUP BY。
这说明 UNION 其实就是 UNION ALL 加上去重步骤。
SELECT DISTINCT * FROM (
SELECT city FROM a
UNION ALL
SELECT city FROM b
) combined;选择合适的模拟方式
决策指南:
- INTERSECT →
EXISTS或 INNER JOIN + DISTINCT。 - EXCEPT →
NOT EXISTS或 LEFT JOIN / IS NULL。 - 当可能存在 NULL 值时,避免使用
NOT IN。 - UNION → 在 DISTINCT 中包裹 UNION ALL。
EXISTS / NOT EXISTS 是最具可移植性且能安全处理 NULL 的写法,因此是面试中最稳妥的回答。
串联起来
能够将集合运算符转换为连接,说明您理解的是集合逻辑,而不只是语法。反连接(LEFT JOIN / IS NULL 或 NOT EXISTS)是最有价值的模式:它既会出现在 EXCEPT 模拟中,也会出现在查找孤儿记录和缺失记录的问题中。
请先以 NOT EXISTS 开头以确保正确性,然后在讨论性能时提到连接写法。
快速检查
您的数据库不支持 EXCEPT。您需要找出 orders_2023 中不在 orders_2024 中的 customer_ids,并且该列可能包含 NULL 值。
回顾
关键要点:
INTERSECT→ INNER JOIN + DISTINCT,或EXISTS。EXCEPT→ LEFT JOIN / IS NULL,或NOT EXISTS(反连接)。- 添加
DISTINCT,以匹配集合运算符的去重行为,并抑制连接扇出。 - 在可能存在 NULL 值时避免使用
NOT IN;优先使用 NOT EXISTS。 UNION= 在 DISTINCT 中包裹 UNION ALL。
用 AI 导师学习 Coding Interview Prep — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 90
- 课程
- 360
常见问题解答
「使用连接模拟集合运算」课时是免费的吗?
是的 — 「使用连接模拟集合运算」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Coding Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 Coding Interview Prep 课程共包含 4 节课。
「使用连接模拟集合运算」这节课中我会学到什么?
在不支持 EXCEPT 和 INTERSECT 的方言中重写它们 你通过在浏览器中直接运行的动手代码来练习 Coding Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Coding Interview Prep 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Coding Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「使用连接模拟集合运算」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Coding Interview Prep 课中编写并运行代码吗?
能。每节 Coding Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。