Logo

新人日誌

首頁關於我部落格

新人日誌

Logo

網站會不定期發佈技術筆記、職場心得相關的內容,歡迎關注本站!

網站
首頁關於我部落格
部落格
分類系列文

© 新人日誌. All rights reserved. 2020-present.

跨表單查詢:四種 JOIN 的差別與使用時機

最後更新:2026年2月3日資料庫

上一篇我們學了 JOIN,可以把兩張表合併在一起。

但 JOIN 有一個盲點,在某些情況下會讓你的資料無聲無息地消失。

這篇來看這個問題是什麼,以及怎麼解決。

JOIN 的盲點:沒有對應的資料會消失

回顧一下我們的會員表和訂單表:

會員表:

會員ID姓名信箱
1Johnjohn@mail.com
2Marymary@mail.com
3Tomtom@mail.com
姓名John
信箱john@mail.com
姓名Mary
信箱mary@mail.com
姓名Tom
信箱tom@mail.com

訂單表:

訂單ID會員ID金額
1160,000
2120,000
3255,000
會員ID1
金額60,000
會員ID1
金額20,000
會員ID2
金額55,000

注意:Tom(會員ID = 3)沒有下過任何訂單,所以訂單表裡面沒有他的資料。

當我們用 JOIN 合併這兩張表:

SELECT *
FROM 會員 JOIN 訂單 ON 會員.會員ID = 訂單.會員ID

合併結果:

會員ID姓名信箱訂單ID金額
1Johnjohn@mail.com160,000
1Johnjohn@mail.com220,000
2Marymary@mail.com355,000
姓名John
信箱john@mail.com
訂單ID1
金額60,000
姓名John
信箱john@mail.com
訂單ID2
金額20,000
姓名Mary
信箱mary@mail.com
訂單ID3
金額55,000

Tom 消失了。

為什麼?

回想上一篇學過的:JOIN 底層做的事情,就是先排列組合,再用合併條件篩選。

把所有組合列出來:

會員ID姓名訂單ID訂單.會員ID金額滿足合併條件?
1John1160,000✅
1John2120,000✅
1John3255,000
2Mary1160,000
2Mary2120,000
2Mary3255,000✅
3Tom1160,000
3Tom2120,000
3Tom3255,000
姓名John
訂單ID1
訂單.會員ID1
金額60,000
滿足合併條件?✅
姓名John
訂單ID2
訂單.會員ID1
金額20,000
滿足合併條件?✅
姓名John
訂單ID3
訂單.會員ID2
金額55,000
滿足合併條件?
姓名Mary
訂單ID1
訂單.會員ID1
金額60,000
滿足合併條件?
姓名Mary
訂單ID2
訂單.會員ID1
金額20,000
滿足合併條件?
姓名Mary
訂單ID3
訂單.會員ID2
金額55,000
滿足合併條件?✅
姓名Tom
訂單ID1
訂單.會員ID1
金額60,000
滿足合併條件?
姓名Tom
訂單ID2
訂單.會員ID1
金額20,000
滿足合併條件?
姓名Tom
訂單ID3
訂單.會員ID2
金額55,000
滿足合併條件?

合併條件是 會員.會員ID = 訂單.會員ID。

John 和 Mary 都有滿足條件的組合,所以出現在合併結果裡。

Tom 的會員ID 是 3,但訂單表裡面沒有任何一筆訂單的會員ID 是 3,所以九個組合裡面,Tom 那三列全部都不滿足條件。

結果就是:Tom 整個人從結果裡消失了。

JOIN 資料消失會造成什麼問題?

你可能會想:「Tom 沒下單,消失就消失啊,有差嗎?」

來看一個情境:

老闆說:「我想看所有會員的客單價分佈,沒有下單的會員,客單價就算 0 元。」

也就是說,老闆期望看到的結果長這樣:

姓名客單價
John40,000
Mary55,000
Tom0
客單價40,000
客單價55,000
客單價0

但我們用 JOIN 合併之後,Tom 整個人消失了,合併結果長這樣:

會員ID姓名信箱訂單ID金額
1Johnjohn@mail.com160,000
1Johnjohn@mail.com220,000
2Marymary@mail.com355,000
姓名John
信箱john@mail.com
訂單ID1
金額60,000
姓名John
信箱john@mail.com
訂單ID2
金額20,000
姓名Mary
信箱mary@mail.com
訂單ID3
金額55,000

Tom 不在這張表裡面。

你可能會想:「那我用逢空補值,遇到空的就補 0 不就好了?」

問題是,逢空補值的前提是「資料要在,只是值是空的」。

但現在 Tom 這筆資料根本不存在,整列都消失了,連補值的機會都沒有。

內部合併(INNER JOIN)

我們前面學的 JOIN,其實完整的名字叫做 INNER JOIN(內部合併)。

為什麼叫「內部」?

因為 INNER 這個字有「向內縮」的意思,可以想像一個漏斗:兩張表的資料從上面倒進去,但漏斗的出口很窄,只有在兩張表都找得到的資料才能通過,其餘的全部被擋在外面丟掉。

所以叫「內部」,就是因為它把結果縮到了最核心的部分:只留下兩邊都有的,其他全部排除。

也就是說:

  • 會員表的資料,要在訂單表找到對應,才會出現
  • 訂單表的資料,要在會員表找到對應,才會出現

任何一邊找不到對應,那筆資料就消失。

像 Tom 在訂單表找不到對應,所以他就消失了。

你可以把它想成「取交集」:只取兩邊都有的部分。

平常我們寫的:

表單A JOIN 表單B ON 合併條件

其實就是:

表單A INNER JOIN 表單B ON 合併條件

INNER 可以省略不寫,效果一樣。

外部合併(OUTER JOIN):保留沒有對應的資料

既然內部合併會把沒有對應的資料排除掉,那我們需要一種方法,把這些資料保留下來。

這種方法叫做 外部合併(OUTER JOIN)。

OUTER 的意思是「外部的」,跟 INNER(內部的)相反。

INNER JOIN 把結果縮到最核心的部分,只留下兩邊都有的資料。那些只存在於某一邊、在另一邊找不到對應的資料,就被排除在核心之外。

OUTER JOIN 則是把範圍往外擴展,把這些在另一邊找不到對應、被 INNER JOIN 排除掉的資料也保留下來。

但保留哪一邊?

回想一下前面的語法:表單A JOIN 表單B ON 合併條件。

合併的時候有兩張表,要保留表單 A?表單 B?還是兩邊都保留?

這裡的「哪一邊」,就是看你書寫的順序:寫在 JOIN 前面的叫左邊,寫在 JOIN 後面的叫右邊。

外部合併根據要保留哪一邊,分成三種:

種類語法保留哪一邊
以左合併LEFT JOIN左邊每一筆都保留,右邊沒對應就填 NULL
以右合併RIGHT JOIN右邊每一筆都保留,左邊沒對應就填 NULL
完全合併FULL JOIN兩邊每一筆都保留,沒對應就填 NULL
語法LEFT JOIN
保留哪一邊左邊每一筆都保留,右邊沒對應就填 NULL
語法RIGHT JOIN
保留哪一邊右邊每一筆都保留,左邊沒對應就填 NULL
語法FULL JOIN
保留哪一邊兩邊每一筆都保留,沒對應就填 NULL

LEFT JOIN 和 RIGHT JOIN 怎麼選?

以我們的例子來說,Tom 是會員,但他沒有下過任何訂單,所以用 INNER JOIN 合併的時候,他在訂單表找不到對應,整個人就消失了。

如果我們想保留所有會員的資料,包含 Tom 這種沒有下單的會員,就不能用 INNER JOIN,要改用 OUTER JOIN。

那要用哪一種?取決於你把會員表寫在哪一邊。

會員寫在左邊:用 LEFT JOIN

SELECT *
FROM 會員 LEFT JOIN 訂單 ON 會員.會員ID = 訂單.會員ID

會員在 JOIN 的左邊,用 LEFT JOIN 就能保留所有會員。

會員寫在右邊:用 RIGHT JOIN

SELECT *
FROM 訂單 RIGHT JOIN 會員 ON 訂單.會員ID = 會員.會員ID

會員在 JOIN 的右邊,用 RIGHT JOIN 一樣能保留所有會員。

兩種寫法,結果是一樣的

-- 這兩個寫法,結果是一樣的
會員 LEFT JOIN 訂單 ON ...
訂單 RIGHT JOIN 會員 ON ...

兩種寫法的合併結果都是:

會員ID姓名信箱訂單ID金額
1Johnjohn@mail.com160,000
1Johnjohn@mail.com220,000
2Marymary@mail.com355,000
3Tomtom@mail.comNULLNULL
姓名John
信箱john@mail.com
訂單ID1
金額60,000
姓名John
信箱john@mail.com
訂單ID2
金額20,000
姓名Mary
信箱mary@mail.com
訂單ID3
金額55,000
姓名Tom
信箱tom@mail.com
訂單IDNULL
金額NULL

Tom 復活了。

不管你用哪種寫法,結果都一樣:會員表的每一筆資料都被保留下來,就算在訂單表找不到對應也不會消失。

Tom 在訂單表找不到對應,所以訂單那邊的欄位自動填 NULL(空值)。

這樣就可以做逢空補值了:遇到金額是 NULL 的時候,補 0 就好。

只是把表的順序調換一下而已。

實務上怎麼寫?一路 LEFT JOIN

既然 LEFT JOIN 和 RIGHT JOIN 結果一樣,那為什麼要有兩種寫法?

因為彈性。

在實務上,大部分的人習慣把主要關注的表放在左邊,然後一路 LEFT JOIN 下去。

什麼意思?假設你現在有三張表:

會員表(主要關注的表):

會員ID姓名
1John
2Mary
3Tom
姓名John
姓名Mary
姓名Tom

訂單表(輔助表):

訂單ID會員ID金額
1160,000
2255,000
會員ID1
金額60,000
會員ID2
金額55,000

客服記錄表(輔助表):

記錄ID會員ID問題
11退貨
23詢問商品
會員ID1
問題退貨
會員ID3
問題詢問商品

你會這樣寫:

FROM 會員
LEFT JOIN 訂單 ON 會員.會員ID = 訂單.會員ID
LEFT JOIN 客服記錄 ON 會員.會員ID = 客服記錄.會員ID

第一步:LEFT JOIN 訂單

LEFT JOIN 訂單 ON 會員.會員ID = 訂單.會員ID

合併結果:

會員ID姓名訂單ID金額
1John160,000
2Mary255,000
3TomNULLNULL
姓名John
訂單ID1
金額60,000
姓名Mary
訂單ID2
金額55,000
姓名Tom
訂單IDNULL
金額NULL

Tom 沒有訂單,但因為是 LEFT JOIN,他被保留下來,訂單欄位填 NULL。

第二步:再 LEFT JOIN 客服記錄

LEFT JOIN 客服記錄 ON 會員.會員ID = 客服記錄.會員ID

在上一步的結果上,再合併客服記錄:

會員ID姓名訂單ID金額記錄ID問題
1John160,0001退貨
2Mary255,000NULLNULL
3TomNULLNULL2詢問商品
姓名John
訂單ID1
金額60,000
記錄ID1
問題退貨
姓名Mary
訂單ID2
金額55,000
記錄IDNULL
問題NULL
姓名Tom
訂單IDNULL
金額NULL
記錄ID2
問題詢問商品

Mary 沒有客服記錄,客服欄位填 NULL。

但三個會員都還在,沒有任何一個人消失。

不管合併幾張表,會員表的資料永遠不會消失,因為它在最左邊。

這就是「一路 LEFT JOIN」的意思:主要的表放在最左邊,後面的輔助表一張一張往右接上去。

FULL JOIN:兩邊都保留

FULL JOIN 就是:不管左邊還是右邊,全部都保留。

SELECT *
FROM 會員 FULL JOIN 訂單 ON 會員.會員ID = 訂單.會員ID

左邊找不到對應?保留,右邊的欄位填 NULL。

右邊找不到對應?保留,左邊的欄位填 NULL。

在我們的例子裡,FULL JOIN 和 LEFT JOIN 的結果是一樣的:

會員ID姓名信箱訂單ID金額
1Johnjohn@mail.com160,000
1Johnjohn@mail.com220,000
2Marymary@mail.com355,000
3Tomtom@mail.comNULLNULL
姓名John
信箱john@mail.com
訂單ID1
金額60,000
姓名John
信箱john@mail.com
訂單ID2
金額20,000
姓名Mary
信箱mary@mail.com
訂單ID3
金額55,000
姓名Tom
信箱tom@mail.com
訂單IDNULL
金額NULL

為什麼結果一樣?回想一下前幾篇學過的 Foreign Key:訂單表的會員ID 參照了會員表的會員ID,所以每一筆訂單的會員ID,在會員表裡一定找得到對應。

不可能出現「有訂單但找不到會員」的情況。

所以 FULL JOIN 多保留的「右邊找不到對應」的情況,在這裡根本不會發生,結果自然跟 LEFT JOIN 一樣。

FULL JOIN 在實務上比較少用,但知道有這個選項就好。

練習:找出沒有養狗的人

來做一個練習。

假設我們有兩張表:

人類表:

人類ID姓名
1小明
2小華
3小美
姓名小明
姓名小華
姓名小美

狗狗表:

狗狗ID狗名飼主ID
1旺財1
2小白1
3阿黃3
狗名旺財
飼主ID1
狗名小白
飼主ID1
狗名阿黃
飼主ID3

一個人可以養很多隻狗,但一隻狗只能登記一個飼主。

人類可能不養狗,狗狗也可能沒有人養(流浪狗)。

問題:找出還沒有養狗的人

想一下:我們要保留哪一邊?

我們要找的是「沒有養狗的人」,也就是:有這個人,但他在狗狗表找不到任何對應。

所以人類的資料我全都要保留,不管他有沒有養狗。

至於沒有人養的狗?我們現在不在意,因為目標是找「沒有養狗的人」,不是找「沒人養的狗」。

把人類放在左邊,用 LEFT JOIN:

SELECT *
FROM 人類 LEFT JOIN 狗狗 ON 人類.人類ID = 狗狗.飼主ID

合併結果:

人類ID姓名狗狗ID狗名飼主ID
1小明1旺財1
1小明2小白1
2小華NULLNULLNULL
3小美3阿黃3
姓名小明
狗狗ID1
狗名旺財
飼主ID1
姓名小明
狗狗ID2
狗名小白
飼主ID1
姓名小華
狗狗IDNULL
狗名NULL
飼主IDNULL
姓名小美
狗狗ID3
狗名阿黃
飼主ID3

小華沒有養狗,所以狗狗那邊的欄位全部是 NULL。

接下來只要篩選「狗狗ID 是 NULL」的資料,就能找出沒有養狗的人:

SELECT 人類.人類ID, 姓名
FROM 人類 LEFT JOIN 狗狗 ON 人類.人類ID = 狗狗.飼主ID
WHERE 狗狗.狗狗ID IS NULL

結果:

人類ID姓名
2小華
姓名小華

這也是一個很常見的做法:用 LEFT JOIN 配合 WHERE ... IS NULL,找出「在另一張表沒有對應」的資料。

不需要寫 OUTER

你可能注意到一件事:我們一直在講「外部合併」,但寫的時候都沒有寫 OUTER。

-- 不需要寫 OUTER
LEFT JOIN        -- 不用寫 LEFT OUTER JOIN
RIGHT JOIN       -- 不用寫 RIGHT OUTER JOIN
FULL JOIN        -- 不用寫 FULL OUTER JOIN

因為你寫了 LEFT、RIGHT 或 FULL,資料庫就知道你在做外部合併了,不需要再多寫 OUTER。

就像 INNER JOIN 可以省略 INNER,直接寫 JOIN 一樣。

四種 JOIN 總整理

寫法完整名稱意思
JOININNER JOIN(內部合併)兩邊都有對應才保留
LEFT JOINLEFT OUTER JOIN(以左合併)左邊全部保留,右邊沒對應填 NULL
RIGHT JOINRIGHT OUTER JOIN(以右合併)右邊全部保留,左邊沒對應填 NULL
FULL JOINFULL OUTER JOIN(完全合併)兩邊都保留,沒對應填 NULL
完整名稱INNER JOIN(內部合併)
意思兩邊都有對應才保留
完整名稱LEFT OUTER JOIN(以左合併)
意思左邊全部保留,右邊沒對應填 NULL
完整名稱RIGHT OUTER JOIN(以右合併)
意思右邊全部保留,左邊沒對應填 NULL
完整名稱FULL OUTER JOIN(完全合併)
意思兩邊都保留,沒對應填 NULL

其中最常用的是 JOIN 和 LEFT JOIN。

不寫任何東西(純 JOIN)就是內部合併,這是預設值,也是最嚴格的合併方式:只要任何一邊沒有對應,就排除。

為什麼預設是最嚴格的?因為資料庫的設計哲學是「沒有特別指定,就做最小量的合併」,比較安全。

小結

這篇的重點只有一個:JOIN 會把沒有對應的資料排除掉。

如果你需要保留這些資料,就用 LEFT JOIN。

什麼時候用 JOIN,什麼時候用 LEFT JOIN?

問自己一個問題:「如果某筆資料在另一張表找不到對應,我還需要它嗎?」

  • 不需要 → 用 JOIN(內部合併)
  • 需要 → 用 LEFT JOIN(以左合併)

大部分的情境下,你會把主要關注的表放在左邊,然後一路 LEFT JOIN 下去。

主要的表「我全都要」,輔助的表「有就拿,沒有就填 NULL」。

這是實務上最常見的寫法。

目前還沒有留言,成為第一個留言的人吧!

發表留言

留言將在審核後顯示。

資料庫

目錄

  • JOIN 的盲點:沒有對應的資料會消失
  • JOIN 資料消失會造成什麼問題?
  • 內部合併(INNER JOIN)
  • 外部合併(OUTER JOIN):保留沒有對應的資料
  • LEFT JOIN 和 RIGHT JOIN 怎麼選?
  • 會員寫在左邊:用 LEFT JOIN
  • 會員寫在右邊:用 RIGHT JOIN
  • 兩種寫法,結果是一樣的
  • 實務上怎麼寫?一路 LEFT JOIN
  • 第一步:LEFT JOIN 訂單
  • 第二步:再 LEFT JOIN 客服記錄
  • FULL JOIN:兩邊都保留
  • 練習:找出沒有養狗的人
  • 問題:找出還沒有養狗的人
  • 不需要寫 OUTER
  • 四種 JOIN 總整理
  • 小結
  • 什麼時候用 JOIN,什麼時候用 LEFT JOIN?