一张图搞懂 SQL JOIN
2025年11月12日
SQL JOIN 可以用来连接两个或多个表,以便在一次查询中取得来自多个表的资料。在实际开发中,我们经常需要从多张表中组合出所需资料,而 JOIN 正是完成这项任务的核心工具。
从本质来看,SQL JOIN 对两个资料列集合,进行集合运算,接受两个集合,再产生一个新的集合。JOIN 会根据两个表格中的关联栏位,以及 JOIN 的方式,来取得对应的资料。举例来说,假如有一张 users 表和一张 orders 表,如果要查询「每个使用者及该使用者的订单」,就可以透过 JOIN 来做到。
在众多学习 SQL JOIN 的初学教材中,最推荐 Visual JOIN 这个小工具,用视觉化的方式带读者理解不同类型的 JOIN,分别可以拿到哪些资料。
除此之外,在社群中有流传一张 SQL JOIN 大抄,一次汇整各类常见的 JOIN 方法,让我们一起来看看。为了让读者们更容易理解,以下我们用 users 与 orders 这两张表为例。
首先是 users 这张表,有 user_id、name,以及 email:
user_id | name | email
--------|-----------|------------------
1 | 王小明 | ming@email.com
2 | 李小美 | mei@email.com
3 | 张大华 | hua@email.com
4 | 陈小新 | new@email.com
接着是 orders 这张表,有 order_id、user_id、amount、date 等栏位:
order_id | user_id | amount | date
---------|---------|--------|------------
101 | 1 | 1500 | 2025-01-15
102 | 1 | 2300 | 2025-02-20
103 | 2 | 800 | 2025-03-10
104 | NULL | 500 | 2025-03-15 (访客结帐)
拿到所有资料的 FULL JOIN

假如我们想要拿到两张表的所有资料,可以透过 FULL JOIN,这样即使没有匹配,也会保留两张表的所有内容。
-- 概念来说 --
SELECT *
FROM A
FULL JOIN B ON A.key = B.key;
-- 以使用者与订单为例 --
SELECT *
FROM users
FULL JOIN orders ON users.user_id = orders.user_id
-- 会拿到 --
user_id | name | order_id | user_id | amount
--------|-------------|----------|---------|-------
1 | 王小明 | 101 | 1 | 1500
1 | 王小明 | 102 | 1 | 2300
2 | 李美丽 | 103 | 2 | 800
3 | 张大华 | NULL | NULL | NULL
4 | 陈小新 | NULL | NULL | NULL
NULL | NULL | 104 | NULL | 500
可以看到,即使张大华与陈小新两个人,都没有下单任何东西,仍然会出现在表上。同时,即使某位访客下单了编号 104 的订单,因为是访客没有使用者 id,但因为是用 FULL JOIN,所以仍会出现在结果中。
只拿到匹配资料的 INNER JOIN

接着,如果想要拿到两张表有匹配的资料,可以用 INNER JOIN。所谓的匹配是指两边都有,假如一个使用者没有下单任何东西,把两张表用 INNER JOIN,就不会出现该使用者。假如某个订单没有对应的使用者 (例如访客订单),也不会出现。
-- 概念来说 --
SELECT *
FROM A
INNER JOIN B ON A.key = B.key;
-- 以使用者与订单为例 --
SELECT *
FROM users
INNER JOIN orders ON users.user_id = orders.user_id
-- 会拿到 --
user_id | name | order_id | user_id | amount
--------|---------|----------|---------|-------
1 | 王小明 | 101 | 1 | 1500
1 | 王小明 | 102 | 1 | 2300
2 | 李美丽 | 103 | 2 | 800
可以看到在 INNER JOIN 下,张大华跟陈小新没出现,因为他们没有任何订单;同时订单编号 104 的订单也没出现,因为是访客帐号没有使用者 id。
拿两边没交集的资料

如果想要反过来,不是拿两边有匹配的资料,而是只要拿两边没有匹配的资料,则可以透过以下的方式做到。
-- 概念来说 --
SELECT *
FROM A
FULL JOIN B ON A.key = B.key
WHERE A.key IS NULL OR B.key IS NULL;
-- 以使用者与订单为例 --
SELECT *
FROM users
FULL JOIN orders ON users.user_id = orders.user_id
WHERE users.user_id IS NULL OR orders.user_id IS NULL
-- 会拿到 --
user_id | name | order_id | user_id | amount
--------|-------------|----------|---------|-------
3 | 张大华 | NULL | NULL | NULL
4 | 陈小新 | NULL | NULL | NULL
NULL | NULL | 104 | NULL | 500
最常用的 LEFT JOIN

在实务工作中,经常会要找不对称的资料,例如「从没有下过单的使用者」,在这种状况下,就需要用上 LEFT JOIN。
具体来说,LEFT JOIN 做的事情是拿到 A 表中所有的资料,然后 B 表有交集的就顺便带上。以这边的例子来说,用 LEFT JOIN 就代表「拿所有使用者的资料,如果有订单的话,就一并拿,没有的话也没关系」。
-- 概念来说 --
SELECT *
FROM A
LEFT JOIN B ON A.key = B.key;
-- 以使用者与订单为例 --
SELECT *
FROM users
LEFT JOIN orders ON users.user_id = orders.user_id
-- 会拿到 --
user_id | name | order_id | user_id | amount
--------|-------------|----------|---------|-------
1 | 王小明 | 101 | 1 | 1500
1 | 王小明 | 102 | 1 | 2300
2 | 李美丽 | 103 | 2 | 800
3 | 张大华 | NULL | NULL | NULL
4 | 陈小新 | NULL | NULL | NULL
但只单靠 LEFT JOIN 没办法拿到「从没有下过单的使用者」,我们需要进一步做筛选。这时可以搭配 WHERE B.key IS NULL;,把没有下单的使用者筛出来。

-- 概念来说 --
SELECT *
FROM A
LEFT JOIN B ON A.key = B.key
WHERE B.key IS NULL;
-- 以使用者与订单为例 --
SELECT *
FROM users
LEFT JOIN orders ON users.user_id = orders.user_id
WHERE orders.order_id IS NULL
-- 会拿到 --
user_id | name | order_id | user_id | amount
--------|-------------|----------|---------|-------
3 | 张大华 | NULL | NULL | NULL
4 | 陈小新 | NULL | NULL | NULL
这个概念可以延伸到很多地方,例如要找「哪些商品没有库存」、「哪些会员没登入过」、「哪些广告没被点击过」,都可以用同样的逻辑来找。从产品面来说,就可以去找出「注册超过 30 天但从未购买的使用者」,发送优惠券刺激购买。
拿到右表所有内容的 RIGHT JOIN

最后让我们来看与 LEFT JOIN 相对的 RIGHT JOIN ,透过 RIGHT JOIN,我们可以拿到 B 表的所有资料,如果 A 表有的话就顺便拿。
以使用者与订单的例子来说,会拿到所有订单,即使没有使用者也一样会拿。所以在下面的例子中,会看到张大华与陈小新没有出现,因为这两位使用者没有订单。
-- 概念来说 --
SELECT *
FROM A
RIGHT JOIN B ON A.key = B.key;
-- 以使用者与订单为例 --
SELECT *
FROM users
RIGHT JOIN orders ON users.user_id = orders.user_id
-- 会拿到 --
user_id | name | order_id | user_id | amount
--------|-------------|----------|---------|-------
1 | 王小明 | 101 | 1 | 1500
1 | 王小明 | 102 | 1 | 2300
2 | 李美丽 | 103 | 2 | 800
NULL | NULL | 104 | NULL | 500

同样地,如果想要进一步拿没有使用者的订单 (例如访客订单),可以搭配 WHERE 语句来做到。在下方的例子可以看到,订单编号 104 的订单没有使用者。
-- 概念来说 --
SELECT *
FROM A
RIGHT JOIN B ON A.key = B.key
WHERE A.key IS NULL;
-- 以使用者与订单为例 --
SELECT *
FROM users
RIGHT JOIN orders ON users.user_id = orders.user_id
WHERE users.user_id IS NULL
user_id | name | order_id | user_id | amount
--------|------|----------|---------|-------
NULL | NULL | 104 | NULL | 500
在实务上,虽然可以用 RIGHT JOIN,但多数开发者会以 LEFT JOIN 为主,因为思考起来比较直觉。当读到 FROM users RIGHT JOIN orders 时,因为是从左读到右,因此自然会先看到 users,但实际上 orders 才是 RIGHT JOIN 的主角,所以这让人感到有点违反直觉。
阅读更多
如果你想成为技术能力更扎实的后端工程师,欢迎加入 E+ 成长计划;透过深度内容与线上课程,打好技术基础,在职涯中持续成长。