分享到

簡介

雖然為了效能和一致性的目的,將資料分隔到離散的表格通常很有用,但您經常需要查詢多個表格的資料,才能回覆特定的請求。合併 (Joining) 表格是一種透過比對基於共同欄位值的每個記錄,來結合來自不同表格的資料的方法。

有幾種不同類型的 JOIN,它們提供各種結合表格記錄的方式。在本文中,我們將介紹 MySQL 如何實作 JOIN,並討論每種 JOIN 最有用的情境。

什麼是 JOIN?

簡而言之,JOIN 是一種顯示來自多個表格資料的方式。它們透過基於特定欄位中相符的值,將來自不同來源的記錄縫合在一起來達成此目的。每個產生的列都包含來自第一個表格的記錄,以及來自第二個表格的列,這是基於每個表格中一個或多個欄位具有相同的值。

JOIN 的基本語法如下

SELECT
*
FROM
<first_table>
<join_type> <second_table>
<join_condition>;

在 JOIN 中,每個產生的列都是透過包含第一個表格的所有欄位,然後是第二個表格的所有欄位來建構的。查詢的 SELECT 部分可用於指定您希望顯示的確切欄位。

如果用於比較的欄位中的值不是唯一的,則可能會從原始表格建構多個列。例如,想像一下,您有一個要比較的欄位來自第一個表格,其中有兩個記錄的值為「紅色」。與此匹配的是第二個表格的欄位,其中有三列的值為「紅色」。JOIN 將為該值產生六個不同的列,代表可以實現的各種組合。

JOIN 的類型和 JOIN 條件決定了顯示的每個列是如何建構的。這會影響來自每個表格的列,這些列在 JOIN 條件上沒有匹配時會發生什麼情況。

為了方便起見,許多 JOIN 將一個表格的主鍵與第二個表格上相關聯的外鍵匹配。雖然主鍵和外鍵僅由資料庫系統用於維護一致性保證,但它們的關係通常使它們成為 JOIN 條件的良好候選。

不同類型的 JOIN

有各種 JOIN 類型可用,每種類型都可能產生不同的結果。了解每種類型是如何建構的,將有助於您確定哪種類型適合不同的情境。

內部 JOIN 和交叉 JOIN

預設的 JOIN 稱為內部 JOIN。在 MySQL 中,可以使用 INNER JOIN、僅 JOINCROSS JOIN 來指定。對於其他資料庫系統,INNER JOINCROSS JOIN 通常是兩個獨立的概念,但 MySQL 在相同的建構中實作它們。

以下是一個典型的範例,示範內部 JOIN 的語法

SELECT
*
FROM
table_1
[INNER] JOIN table_2
ON table_1.id = table_2.table_1_id;

內部 JOIN 是限制性最強的 JOIN 類型,因為它只顯示透過結合每個表格中的列所建立的列。組成表格中任何在另一個表格中沒有匹配對應項的列都會從結果中移除。例如,如果第一個表格在比較欄位中的值為「藍色」,而第二個表格沒有具有該值的記錄,則該列將從輸出中隱藏。

如果您將結果表示為組成表格的 Venn 圖,則內部 JOIN 允許您表示兩個圓圈的重疊區域。僅存在於其中一個表格中的任何值都不會顯示。

如上所述,MySQL 也使用此格式來產生交叉 JOIN。在 MySQL 中,您可以使用沒有任何匹配條件的內部 JOIN 來產生交叉 JOIN。交叉 JOIN 不使用任何比較來確定每個表格中的列是否彼此匹配。相反地,結果是透過簡單地將第一個表格中的每個列添加到第二個表格的每個列來建構的。

這會產生兩個或多個表格中列的笛卡爾積。實際上,這種 JOIN 樣式會無條件地結合來自每個表格的列。因此,如果每個表格都有三列,則產生的表格將有九列,其中包含來自兩個表格的所有欄位。

例如,如果您有一個名為 t1 的表格,與一個名為 t2 的表格結合,每個表格都有列 r1r2r3,則結果將是九列,如下所示結合

t1.r1 + t2.r1
t1.r1 + t2.r2
t1.r1 + t2.r3
t1.r2 + t2.r1
t1.r2 + t2.r2
t1.r2 + t2.r3
t1.r3 + t2.r1
t1.r3 + t2.r2
t1.r3 + t2.r3

左側 JOIN

左側 JOIN 是一種 JOIN,它顯示內部 JOIN 中找到的所有記錄,以及來自第一個表格的所有不匹配的列。在 MYSQL 中,可以指定為 LEFT OUTER JOIN 或僅指定為 LEFT JOIN

左側 JOIN 的基本語法遵循此模式

SELECT
*
FROM
table_1
LEFT JOIN table_2
ON table_1.id = table_2.table_1_id;

左側 JOIN 的建構方式是先執行內部 JOIN,從兩個表格中的所有相符記錄建構列。之後,也包含來自第一個表格的不匹配記錄。由於 JOIN 中的每個列都包含兩個表格的欄位,因此不匹配的欄位使用 NULL 作為第二個表格中所有欄位的值。

如果您將結果表示為組成表格的 Venn 圖,則左側 JOIN 允許您表示整個左側圓圈。左側圓圈中由兩個圓圈之間的交集表示的部分將具有由右側表格補充的其他資料。

右側 JOIN

右側 JOIN 是一種 JOIN,它顯示內部 JOIN 中找到的所有記錄,以及來自第二個表格的所有不匹配的列。在 MySQL 中,可以指定為 RIGHT OUTER JOIN 或僅指定為 RIGHT JOIN

右側 JOIN 的基本語法遵循此模式

SELECT
*
FROM
table_1
RIGHT JOIN table_2
ON table_1.id = table_2.table_1_id;

右側 JOIN 的建構方式是先執行內部 JOIN,從兩個表格中的所有相符記錄建構列。之後,也包含來自第二個表格的不匹配記錄。由於 JOIN 中的每個列都包含兩個表格的欄位,因此不匹配的欄位使用 NULL 作為第一個表格中所有欄位的值。

如果您將結果表示為組成表格的 Venn 圖,則右側 JOIN 允許您表示整個右側圓圈。右側圓圈中由兩個圓圈之間的交集表示的部分將具有由左側表格補充的其他資料。

為了便於移植,MySQL 建議您在可能的情況下使用左側 JOIN 而不是右側 JOIN。

完整 JOIN

完整 JOIN 是一種 JOIN,它顯示內部 JOIN 中找到的所有記錄,以及來自兩個組成表格的所有不匹配的列。MySQL 原生不實作完整 JOIN,但我們可以使用一些技巧來模擬其行為。

為了複製完整外部 JOIN 的結果,我們將執行左側 JOIN,以取得兩個表格共用的所有結果,以及來自左側表格的所有不匹配列。然後,我們將使用 UNION ALL 集合運算子,將這些結果與右側表格的「反 JOIN」結合。「反 JOIN」是一種 JOIN 運算,專門尋找表格之間共有的結果。

完整 JOIN 的基本語法遵循此模式

( SELECT
*
FROM
table_1
LEFT JOIN table_2
ON table_1.id = table_2.table_1_id
)
UNION ALL
( SELECT
*
FROM
table_1
RIGHT JOIN table_2
ON table_1.id = table_2.table_1_id
WHERE table_1.id IS NULL
);

由於 JOIN 中的每個列都包含兩個表格的欄位,因此不匹配的欄位使用 NULL 作為不匹配的另一個表格中所有欄位的值。

如果您將結果表示為組成表格的 Venn 圖,則完整 JOIN 允許您完整表示兩個組成圓圈。兩個圓圈的交集將具有由每個組成表格提供的值。圓圈中重疊區域外部的部分將具有來自它們所屬表格的值,並使用 NULL 來填入在另一個表格中找到的欄位。

自我 JOIN

自我 JOIN 是任何將表格的列與自身結合的 JOIN。可能不容易立即看出這有什麼用處,但它實際上有很多常見的應用。

通常,表格描述可以履行彼此關係中多個角色的實體。例如,如果您有一個 people 表格,則每列都可能包含一個 mother 欄位,該欄位參考表格中的其他 people。自我 JOIN 允許您透過將表格的第二個實例 JOIN 到第一個實例來將這些不同的列縫合在一起,其中這些值匹配。

由於自我 JOIN 兩次參考相同的表格,因此需要表格別名來消除參考的歧義。例如,在上面的範例中,您可以透過使用別名 people AS childrenpeople AS mothers 來 JOIN people 表格的兩個實例。這樣,您就可以在定義 JOIN 條件時指定您要參考的表格實例。

這是另一個範例,這次表示員工和經理之間的關係

SELECT
*
FROM
people AS employee
JOIN people AS manager
ON employee.manager_id = manager.id;

JOIN 條件

在合併表格時,JOIN 條件決定了如何將列匹配在一起以形成複合結果。基本前提是定義每個表格中必須匹配的欄位,才能在該列上發生 JOIN。

ON 子句

定義表格 JOIN 條件最標準的方式是使用 ON 子句。ON 子句使用等號來指定每個表格中的確切欄位,這些欄位將被比較以確定何時可能發生 JOIN。MySQL 使用提供的欄位將每個表格中的列縫合在一起。

ON 子句是最冗長的,但也是可用的 JOIN 條件中最靈活的。無論要合併的每個表格的欄位名稱標準化程度如何,它都允許特異性。

ON 子句的基本語法如下所示

SELECT
*
FROM
table1
JOIN
table2
ON
table1.id = table2.ident;

在這裡,每當 table1id 欄位與 table2ident 欄位相符時,table1table2 中的列將被 JOIN 在一起。由於使用了內部 JOIN,因此結果將僅顯示已 JOIN 的列。由於查詢使用了萬用字元 *,因此將顯示兩個表格的所有欄位。

這表示將顯示 table1id 欄位和 table2ident 欄位,即使它們由於滿足 JOIN 條件而具有完全相同的值。您可以透過在 SELECT 欄位清單中呼叫您希望顯示的確切欄位來避免這種重複。

USING 子句

USING 子句是用於指定 ON 子句條件的簡寫,當要比較的欄位在兩個表格中具有相同的名稱時可以使用。USING 子句採用括在括號中的共用欄位名稱清單,這些欄位名稱應進行比較。

USING 子句的一般語法使用此格式

SELECT
*
FROM
table1
JOIN
table2
USING
(id, state);

當兩個表格共有的兩個欄位(idstate)各自具有匹配的值時,此 JOIN 會將 table1table2 結合。

可以使用 ON 更詳細地表示相同的 JOIN,如下所示

SELECT
*
FROM
table1
JOIN
table2
ON
table1.id = table2.id AND table1.state = table2.state;

雖然上述兩個 JOIN 都會導致建構相同的列,並呈現相同的資料,但它們的顯示方式會略有不同。雖然 ON 子句包含兩個表格的所有欄位,但 USING 子句會隱藏重複的欄位。因此,結果不會有兩個單獨的 id 欄位和兩個單獨的 state 欄位(每個表格一個),而只會有每個共用欄位的一個,然後是 table1table2 提供的所有其他欄位。

NATURAL 子句

NATURAL 子句是另一個簡寫,可以進一步減少 USING 子句的冗長性。NATURAL JOIN 不指定要匹配的任何欄位。相反地,MySQL 將根據每個資料庫中具有匹配欄位的所有欄位自動 JOIN 表格。

NATURAL JOIN 子句的一般語法如下所示

SELECT
*
FROM
table1
NATURAL JOIN
table2;

假設 table1table2 都具有名為 idstatecompany 的欄位,則上述查詢將等效於使用 ON 子句的此查詢

SELECT
*
FROM
table1
JOIN
table2
ON
table1.id = table2.id AND table1.state = table2.state AND table1.company = table2.company;

以及使用 USING 子句的此查詢

SELECT
*
FROM
table1
JOIN
table2
USING
(id, state, company);

USING 子句一樣,NATURAL 子句會隱藏重複的欄位,因此在結果中只會有每個 JOIN 欄位的單個實例。

雖然 NATURAL 子句可以減少查詢的冗長性,但在使用它時必須謹慎。由於用於 JOIN 表格的欄位是自動計算的,因此如果組成表格中的欄位發生變更,則由於新的 JOIN 條件,結果可能會大相逕庭。

JOIN 條件和 WHERE 子句

JOIN 條件與使用 WHERE 子句篩選資料列的比較具有許多共同的特徵。這兩個建構都定義了必須評估為 true 的表達式,才能將列視為符合條件。因此,在 WHERE 建構中包含其他比較與在 JOIN 子句本身中定義它們之間的區別並不總是直觀的。

為了理解將產生的差異,我們必須查看 MySQL 處理查詢不同部分的順序。在這種情況下,首先處理 JOIN 條件中的述詞,以在記憶體中建構虛擬 JOIN 表格。在此階段之後,將評估 WHERE 子句中的表達式以篩選產生的列。

舉例來說,假設我們有兩個名為 customersorders 的表格,我們需要將它們 JOIN 在一起。我們想要透過將 customers.id 欄位與 orders.customer_id 欄位相符來 JOIN 這兩個表格。此外,我們對 orders 表格中 product_id 為 12345 的列感興趣。

鑑於上述需求,我們有兩個關心的條件。但是,我們表達這些條件的方式將決定我們收到的結果。

首先,讓我們將兩者都用作 LEFT JOIN 的 JOIN 條件

SELECT
customers.id AS customers_id,
customers.name,
orders.id AS orders_id,
orders.product_id
FROM
customers
LEFT JOIN
orders
ON
customers.id = orders.customers_id AND orders.product_id = 12345;

結果可能看起來像這樣

+--------------+----------+-----------+------------+
customers_id | name | orders_id | product_id |
+--------------+----------+-----------+------------+
20 | Early Co | NULL | NULL |
320 | Other Co | 680 | 12345 |
4380 | Acme Co | 182 | 12345 |
4380 | Acme Co | 480 | 12345 |
8033 | Big Co | NULL | NULL |
+--------------+----------+-----------+------------+
5 rows in set (0.00 sec)

MySQL 透過執行以下運算得出此結果

  1. customers 表格中的任何列與 orders 表格 JOIN 在一起,其中
    • customers.idorders.customers_id 相符。
    • orders.product_id 與 12345 相符
  2. 由於我們正在使用左側 JOIN,因此包含來自左側表格 (customers) 的任何不匹配的列,並使用 NULL 值填補來自右側表格 (orders) 的欄位。
  3. 僅顯示 SELECT 欄位規格中列出的欄位。

結果是我們所有 JOIN 的列都符合我們正在尋找的兩個條件。但是,左側 JOIN 會導致 MySQL 也包含來自第一個表格的任何未滿足 JOIN 條件的列。這會導致「剩餘」的列,這些列似乎不符合查詢的明顯意圖。

如果我們將第二個查詢 (orders.product_id = 12345) 移至 WHERE 子句,而不是將其包含為 JOIN 條件,我們將獲得不同的結果

SELECT
customers.id AS customers_id,
customers.name,
orders.id AS orders_id,
orders.product_id
FROM
customers
LEFT JOIN
orders
ON
customers.id = orders.customers_id
WHERE
orders.product_id = 12345;

這次,僅顯示三列

+--------------+----------+-----------+------------+
customers_id | name | orders_id | product_id |
+--------------+----------+-----------+------------+
4380 | Acme Co | 182 | 12345 |
4380 | Acme Co | 480 | 12345 |
320 | Other Co | 680 | 12345 |
+--------------+----------+-----------+------------+
3 rows in set (0.00 sec)

比較的執行順序是造成這些差異的原因。這次,MySQL 像這樣處理查詢

  1. customers 表格中的任何列與 orders 表格 JOIN 在一起,其中 customers.idorders.customers_id 相符。
  2. 由於我們正在使用左側 JOIN,因此包含來自左側表格 (customers) 的任何不匹配的列,並使用 NULL 值填補來自右側表格 (orders) 的欄位。
  3. 評估 WHERE 子句以移除任何 orders.product_id 欄位的值不是 12345 的列。
  4. 僅顯示 SELECT 欄位規格中列出的欄位。

這次,即使我們正在使用左側 JOIN,WHERE 子句也會透過篩選掉所有沒有正確 product_id 的列來截斷結果。由於任何不匹配的列都將 product_id 設定為 NULL,因此這會移除所有由左側 JOIN 填入的不匹配列。它還會移除任何透過 JOIN 條件匹配但不符合第二輪檢查的列。

了解 MySQL 用於執行查詢的基本流程可以幫助您避免在處理資料時,犯下一些容易犯但難以偵錯的錯誤。

結論

在本文中,我們討論了聯結 (joins) 是什麼,以及 MySQL 如何實作聯結,作為一種從多個資料表組合記錄的方式。我們涵蓋了可用的不同聯結類型,以及像 ONWHERE 子句等不同條件如何影響資料庫建構結果的方式。

當您越來越熟悉聯結時,您將能夠將它們用作您工具箱中的常規部分,從各種來源提取資料,並將資訊片段拼接在一起,以創建更完整的全貌。聯結有助於將組織原則和效能考量可能分離的資料整合在一起。學習如何有效地使用聯結可以幫助您整合資料,無論資料在系統中如何組織。

關於作者
Justin Ellingwood

Justin Ellingwood

自 2013 年以來,Justin 一直在撰寫關於資料庫、Linux、基礎設施和開發者工具的文章。他目前與妻子和兩隻兔子住在柏林。他通常不必以第三人稱寫作,這對所有相關方來說都是一種解脫。