图 1:本文封面,维恩图就是三种 join 的全部记忆负担。
先说明属性:这是一篇经典题解析,题目出自本地历史面试资料,与任何公司的当前招聘无关。连接查询的种类是 SQL 面试的基本功题,靠背定义容易混,画两个圆就再也不会错。
想象面试现场:「inner join、left join、right join 有什么区别?结果集各是什么?」
第一步,先造两张小表,后面全部用它们说话。
一张用户表 users,三行:id 为 1、2、3。一张订单表 orders,四行,user_id 分别为 1、1、2、99——注意两个刻意设计:用户 1 有两张订单(一对多),订单 99 的用户不存在(孤儿数据)。这两处设计就是后面所有坑的伏笔。
第二步,inner join:只留交集。
SELECT * FROM users u INNER JOIN orders o ON u.id = o.user_id;
结果三行:用户 1 出现两次(配它的两张订单),用户 2 出现一次。用户 3 没订单,消失;订单 99 没主人,也消失。inner join 的语义就是两个圆的交集:两边都匹配上的行才进结果,任何一侧配不上就丢弃。
第三步,left join:左表全留,右边补 NULL。
SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id;
结果四行:交集的三行照旧,加上用户 3——它没有订单,但左表的行必须全部保留,于是右表的列全部填 NULL。left join 保留「左圆全部加交集」。right join 是它的镜像:保留右圆全部加交集,换成它之后订单 99 会带着一排 NULL 的用户列出现。官方文档的建议值得记住:为跨数据库的可移植性,统一用 left join,别用 right join——右外连接随时可以调换表序改写成左外连接。
至于 cross join,它是笛卡尔积:m 行乘 n 行,不带任何匹配条件,本文的三行乘四行就是十二行。业务里几乎不用,知道它存在即可。
第四步,两个高频坑,都藏在「左表全留」里。
坑一:一对多让行数膨胀。用户 1 在 users 里只有一行,left join 之后变成两行——右表有几条匹配就复制几次。做聚合统计时这是最经典的翻车现场:先 join 再 count 用户,结果把订单数算进了用户数。铁律是聚合之前先想清楚粒度:统计用户就在用户粒度上聚合,统计订单就在订单粒度上聚合,需要两边数字时分别聚合再合并,别指望一条 join 之后的大宽表直接 count。
坑二:右表的过滤条件写错位置,外连接退化成内连接。看这两条语句:
-- 写法 A:右表条件写在 WHERE
SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id
WHERE o.amount > 100;
-- 写法 B:右表条件写在 ON
SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id
AND o.amount > 100;
写法 A 里,用户 3 那行的订单列是 NULL,NULL > 100 不为真,这行被 WHERE 过滤掉——左表全留的承诺被打破,效果等同 inner join。写法 B 把条件放进 ON,只影响「右表哪些行参与匹配」,用户 3 依然保留,订单列补 NULL。要保留左表全集,右表的过滤条件必须写进 ON。
顺带一个反向用法:「找没有订单的用户」恰好利用补 NULL 的特性——left join 之后加 WHERE o.user_id IS NULL,官方文档也把这列为外连接的标准用途之一。
图 2:三种 join 的维恩图与两个高频坑。
可能的追问与易错点。
「full outer join 呢?」保留两圆的全部,MySQL 不直接支持,用 left join 加 right join 再 UNION 拼出来。「join 和逗号分隔的表写法一样吗?」没有连接条件时两者都产生笛卡尔积,但逗号优先级低于 join,混用时容易撞上莫名其妙的报错,现代写法一律用显式 join。易错点主要有三:一是以为 left join 的结果行数等于左表行数,被一对多放大打脸;二是把右表过滤条件写进 WHERE,外连接悄悄退化成内连接;三是 join 完之后不检查行数,把膨胀的结果直接喂给下游。
回到面试现场,建议的答题节奏:先画两个圆讲清交集、左圆加交集、右圆加交集三种结果集,再补 cross join 一句话带过,然后主动抛出两个坑——行数放大与 ON/WHERE 之分,这是从「背过」到「用过」的分水岭。这套基本功在 AI 数据准备里天天兑现:样本表关联标注表、用户表关联行为表,left join 误用产生的重复行,是训练数据污染的经典来源。
你在 join 上踩过最疼的坑是哪一个?行数爆炸还是条件写错位置?欢迎分享。