一张图搞懂 SQL JOIN

2025年11月12日

💎 加入 E+ 成長計畫 與超過 800+ 位工程師一同在社群成長,並獲得更多深度的軟體前後端學習資源

SQL JOIN 可以用来连接两个或多个表,以便在一次查询中取得来自多个表的资料。在实际开发中,我们经常需要从多张表中组合出所需资料,而 JOIN 正是完成这项任务的核心工具。

从本质来看,SQL JOIN 对两个资料列集合,进行集合运算,接受两个集合,再产生一个新的集合。JOIN 会根据两个表格中的关联栏位,以及 JOIN 的方式,来取得对应的资料。举例来说,假如有一张 users 表和一张 orders 表,如果要查询「每个使用者及该使用者的订单」,就可以透过 JOIN 来做到。

在众多学习 SQL JOIN 的初学教材中,最推荐 Visual JOIN 这个小工具,用视觉化的方式带读者理解不同类型的 JOIN,分别可以拿到哪些资料。

除此之外,在社群中有流传一张 SQL JOIN 大抄,一次汇整各类常见的 JOIN 方法,让我们一起来看看。为了让读者们更容易理解,以下我们用 usersorders 这两张表为例。

首先是 users 这张表,有 user_idname,以及 email

user_id | name      | email
--------|-----------|------------------
1       | 王小明     | ming@email.com
2       | 李小美     | mei@email.com
3       | 张大华     | hua@email.com
4       | 陈小新     | new@email.com

接着是 orders 这张表,有 order_iduser_idamountdate 等栏位:

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
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

接着,如果想要拿到两张表有匹配的资料,可以用 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

拿两边没交集的资料

image

如果想要反过来,不是拿两边有匹配的资料,而是只要拿两边没有匹配的资料,则可以透过以下的方式做到。

-- 概念来说 --
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

image

在实务工作中,经常会要找不对称的资料,例如「从没有下过单的使用者」,在这种状况下,就需要用上 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;,把没有下单的使用者筛出来。

image
-- 概念来说 --
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

image

最后让我们来看与 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
image

同样地,如果想要进一步拿没有使用者的订单 (例如访客订单),可以搭配 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+ 成长计划;透过深度内容与线上课程,打好技术基础,在职涯中持续成长。

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們