前三篇里,你写的每条查询都只在一张表里转:从 orders 取列、筛行、分组统计。可有一类问题它回答不了——“这些订单是哪位用户下的、从哪个渠道来的”。订单表里只有 user_id 这个数字,用户的昵称和注册渠道根本没记在订单表上,它们记在另一张表里。

把这两张表拼起来看,就是 JOIN 要做的事。

两张表长什么样,靠什么对得上

users 表长这样,每行是一位注册用户:

user_iduser_namesignup_datechannel
8823阿May2024-01-05应用商店
9107老周2023-11-22好友推荐
9310小林2024-02-14广告投放
9402Tony2024-03-08应用商店

orders 表比上一篇又多了一行,现在有 9 行:

order_iduser_idcityamountcreated_at
10018823杭州129.002024-03-01 10:23:11
10029107上海58.502024-03-01 11:07:42
10038823杭州320.002024-03-02 09:15:03
10049310NULL76.002024-03-02 14:02:50
10059107杭州45.002024-03-03 08:40:12
10069107上海210.002024-03-03 19:22:05
10079310上海91.502024-03-04 12:05:33
10088823NULL19.902024-03-04 21:41:07
10099500北京199.002024-03-05 10:11:26

两张表都有 user_id 这一列,而且讲的是同一件事:订单表里它是“这单是谁下的”,用户表里它是“这是哪位用户”。这就是两张表能对上的地方。

顺手看清两处对不上:用户 9402(Tony)在 orders 里一单都没有;订单 1009 的 user_id 是 9500,users 里查不到这个人。这两个“对不上”不是脏数据,而是本篇的核心——两张表拼在一起时,它们各自会走向不同的结局。

JOIN 的动作:拿 ON 的条件,一行一行去配

想给每笔订单补上用户名和渠道:

1SELECT orders.order_id, orders.city, orders.amount,
2       users.user_name, users.channel
3FROM orders
4JOIN users ON orders.user_id = users.user_id;

ON 后面写的是配对规则:orders.user_id = users.user_id。数据库拿到 orders 的每一行,就拿这一行的 user_id 到 users 里找相同的值,找到就把两行合成一行更宽的记录。1001 那一行的 user_id 是 8823,在 users 里找到阿May,于是拼出一行:订单 1001、杭州、129.00,加上阿May、应用商店。

关键的一点:JOIN 的产出是一对一对的行,不是给原表“加了几列”。 阿May 在 orders 里有 3 单,她的昵称和渠道就会在结果里重复出现 3 次——这不是重复数据,而是同一个用户配上了 3 条不同的订单。同理,如果 users 里同一个 user_id 出现了两次,那一单就会配出两行,结果行数凭空翻倍。所以看到 JOIN 之后行数比预想的多,第一件要查的就是“配对另一边的 key 是不是唯一的”。

<figure>
绘制中
</figure>

表名太长,先起个短名字

上面的语句把 orders.user_id、users.user_name 一路写全,读起来很累。给表起个别名就清爽了:

1SELECT o.order_id, o.city, o.amount,
2       u.user_name, u.channel
3FROM orders AS o
4JOIN users AS u ON o.user_id = u.user_id;

orders AS o 的意思是:这条语句里,orders 就叫 o。之后的 o.order_id 就是“orders 表的 order_id 列”。这里的 AS 通常可以省掉,写成 FROM orders o 也一样。

那为什么要写 o. 这个前缀?试试不写:

1SELECT user_id, o.city, u.user_name
2FROM orders o JOIN users u ON o.user_id = u.user_id;

这会报错(MySQL 里是 ERROR 1052: Column 'user_id' in field list is ambiguous)。“ambiguous”意思是“有歧义”:两张表都有 user_id,你说的是哪一个?ON 里的 o.user_id = u.user_id 也正是因为两边都叫这个名字,才必须各带前缀指明。所以规则很简单:两张表里同名的列,写的时候必须用“表别名.列名”限定。

INNER JOIN:两边都配上,这一行才留下

上面那种写法,在 MySQL 里 JOIN 和 INNER JOIN 是同义词 1。INNER JOIN 的规则是:只有 ON 条件成立的那一对行才进结果。9 行订单跑下来只剩 8 行——1009 那条订单的 user_id 是 9500,在 users 里找不到,条件不成立,这一整行被丢掉。

反过来从用户角度看也一样:Tony 一单没下,INNER JOIN 里他不会出现。INNER JOIN 是两边一起筛,任何一边配不上,整行就不进结果。

LEFT JOIN:左边每一行至少出现一次

如果那 8 行订单是你这次分析的全部对象,1009 丢掉也许可以接受。但如果任务是“把 3 月的订单全部拉出来、能补的用户信息就补上”,用户信息查不到这件事不该让订单本身消失。这时候换成:

1SELECT o.order_id, o.city, o.amount,
2       u.user_name, u.channel
3FROM orders o
4LEFT JOIN users u ON o.user_id = u.user_id;

结果变成 9 行。前 8 行和 INNER JOIN 一样,第 9 行对应订单 1009:订单那几列照常显示(北京、199.00),而 u.user_name 和 u.channel 两列填的是 NULL。

规则可以这样记:LEFT JOIN 保证写在它左边的那张表(这里就是 orders)的每一行都在结果里至少出现一次。 右边配不上的时候,就用“所有列都是 NULL”的一行来补位。PostgreSQL 文档里的描述是:从左表出发逐行去找匹配,找不到就用空值代替右表的列 3;MySQL 文档的说法一样——LEFT JOIN 中右表没有匹配行时,会生成一行、其右表所有列都设为 NULL 1。

现在把两张表换个位置写:

1SELECT u.user_name, u.channel, o.order_id, o.amount
2FROM users u
3LEFT JOIN orders o ON u.user_id = o.user_id;

这一句的“保底名单”变成了 users:4 位用户谁都不能丢。结果是 9 行——阿May 3 行(1001、1003、1008)、老周 3 行、小林 2 行、Tony 1 行(订单那几列全是 NULL)。

把这个结果和上一条 LEFT JOIN 放在一起看,是本篇最值得记住的一处对照:两句都返回 9 行,但内容完全不同。 前一句的第 9 行是“找不到用户的订单 1009”,后一句的第 9 行是“没有下过单的用户 Tony”。所以写 LEFT JOIN 时,真正要想清楚的不是“用不用 LEFT”,而是“哪张表是那份不许丢的名单”——答案由你把哪张表写在 JOIN 左边决定。

左边的行什么时候还是会消失

把 LEFT JOIN 理解成“左边一行都不会丢”,很容易接着写出这样的查询:

1SELECT o.order_id, u.user_name
2FROM orders o
3LEFT JOIN users u ON o.user_id = u.user_id
4WHERE u.channel = '广告投放';

想法是“只看广告投放渠道来的订单”。可结果里只有 1004 和 1007——1009 那条订单还是消失了。

原因要回到上一篇讲 NULL 的那一节。1009 在 LEFT JOIN 里配不上 users,它的 u.channel 是 NULL。而 NULL 参与任何比较,结果都不是真也不是假,而是 未知;WHERE 只放行判断为真的行,未知和假一样被丢掉。也就是说,LEFT JOIN 刚刚特意补出来的那一行,马上就被 WHERE 里针对右表列的普通条件筛掉了——效果和 INNER JOIN 一模一样。MySQL 的优化器甚至会在这种条件下,直接把语句改写成内连接来执行 2。

想让条件表达的是“只有广告渠道的用户才配得上”,就该把它写进 ON:

1FROM orders o
2LEFT JOIN users u ON o.user_id = u.user_id
3  AND u.channel = '广告投放';

这时 1009 留下来了:ON 条件不成立,它配不上任何一行,于是右表列补 NULL,但左边这行照样保留。

分界线只有一句话:ON 决定右边的哪些行有资格参与配对;WHERE 决定配对完之后,结果里的哪些行留下。 想清楚你的条件筛的是“匹配资格”还是“最终结果”,就知道该写在哪边。

这里还要补一个容易误伤的边界:并不是“LEFT JOIN 之后就不能写 WHERE”。筛左表自己的列完全没问题,比如加上 WHERE o.city = '上海'——1009 的 city 是北京,被筛掉是你本来的意思,跟 NULL 没有关系。要小心的只是用 WHERE 去筛右表的列,除非你确实是想把整句变回 INNER JOIN 的效果。

顺手能查的一件事:哪些行对不上

LEFT JOIN 补 NULL 这个特性,正好可以用来专门找“配不上的行”:

1SELECT o.order_id, o.user_id
2FROM orders o
3LEFT JOIN users u ON o.user_id = u.user_id
4WHERE u.user_id IS NULL;

结果就是 1009。判断“有没有配上”要用 IS NULL 而不是 = NULL,理由和上一篇里一样。

小结和练习

ON 写配对规则,两边的 key 要在讲同一件事。INNER JOIN 两边都配上才留行;LEFT JOIN 保证紧挨它左边的那张表一行不丢,右表配不上就补 NULL,所以选哪种 JOIN、先写哪张表,本质上是在回答“哪份名单不许丢”。把条件写在 ON 还是 WHERE,决定了它筛的是匹配资格还是最终结果。

练习题:取 2024 年 3 月杭州或上海的订单,带上用户昵称和渠道;并且要求即使查不到用户信息,订单也必须保留。

参考答案
1SELECT o.order_id, o.city, o.amount, u.user_name, u.channel
2FROM orders o
3LEFT JOIN users u ON o.user_id = u.user_id
4WHERE o.created_at >= '2024-03-01'
5  AND o.created_at <  '2024-04-01'
6  AND (o.city = '杭州' OR o.city = '上海');

结果 6 行:1001、1002、1003、1005、1006、1007。1004 和 1008 的城市是空的,城市那一组条件得到未知,被筛掉;1009 是北京,被城市条件筛掉。

注意这个例子里 LEFT JOIN 和 INNER JOIN 的结果完全一样,因为筛剩的这 6 单,用户都在 users 里。要看出两者的差别,就得放宽到能留下 1009 的条件。

三组条件都写在 WHERE 而不是 ON,这是对的——它们筛的都是左表(订单)自己的列,没有哪一组会连带把补 NULL 的行误杀。

下一篇会接着看:当 LEFT JOIN 再配上 GROUP BY 做统计时,为什么同样想数“每个用户多少单”,COUNT(*) 和 COUNT(u.user_id) 会给出不同的答案。