分享到

簡介

從一致性、彈性和某些類型的效能的角度來看,將相關資料拆分到不同的表格中可能是有益的。但是,當相關資訊跨越多個表格時,您仍然需要一種合理的方式來重新整合記錄。

在關聯式資料庫中,Join 提供了一種根據共同欄位值合併兩個或多個表格中的記錄的方法。不同類型的 Join 可以根據應如何處理不匹配的列來實現不同的結果。在本指南中,我們將討論 PostgreSQL 提供的各種 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 都可能產生不同的結果。了解每種類型是如何建構的,將有助於您確定哪種類型適合不同的情況。

Inner Join

預設的 Join 稱為 Inner Join。在 PostgreSQL 中,可以使用 INNER JOIN 或簡稱為 JOIN 來指定。

以下是一個典型的範例,示範 Inner Join 的語法

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

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

如果您將結果表示為組件表格的 Venn 圖,則 Inner Join 允許您表示兩個圓圈的重疊區域。僅在其中一個表格中存在的值都不會顯示。

Left Join

Left Join 是一種 Join,它顯示在 Inner Join 中找到的所有記錄,以及來自第一個表格的所有不匹配列。在 PostgreSQL 中,可以將其指定為 LEFT OUTER JOIN 或僅指定為 LEFT JOIN

Left Join 的基本語法遵循以下模式

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

Left Join 的建構方式是首先執行 Inner Join,以從兩個表格中所有匹配的記錄建構列。之後,也包含來自第一個表格的不匹配記錄。由於 Join 中的每個列都包含兩個表格的欄位,因此不匹配的欄位會使用 NULL 作為第二個表格中所有欄位的值。

如果您將結果表示為組件表格的 Venn 圖,則 Left Join 允許您表示整個左圓圈。由兩個圓圈之間的交集表示的左圓圈部分將具有由右側表格補充的額外資料。

Right Join

Right Join 是一種 Join,它顯示在 Inner Join 中找到的所有記錄,以及來自第二個表格的所有不匹配列。在 PostgreSQL 中,可以將其指定為 RIGHT OUTER JOIN 或僅指定為 RIGHT JOIN

Right Join 的基本語法遵循以下模式

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

Right Join 的建構方式是首先執行 Inner Join,以從兩個表格中所有匹配的記錄建構列。之後,也包含來自第二個表格的不匹配記錄。由於 Join 中的每個列都包含兩個表格的欄位,因此不匹配的欄位會使用 NULL 作為第一個表格中所有欄位的值。

如果您將結果表示為組件表格的 Venn 圖,則 Right Join 允許您表示整個右圓圈。由兩個圓圈之間的交集表示的右圓圈部分將具有由左側表格補充的額外資料。

Full Join

Full Join 是一種 Join,它顯示在 Inner Join 中找到的所有記錄,以及來自兩個組件表格的所有不匹配列。在 PostgreSQL 中,可以將其指定為 FULL OUTER JOIN 或僅指定為 FULL JOIN

Full Join 的基本語法遵循以下模式

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

Full Join 的建構方式是首先執行 Inner Join,以從兩個表格中所有匹配的記錄建構列。之後,也包含來自兩個表格的不匹配記錄。由於 Join 中的每個列都包含兩個表格的欄位,因此不匹配的欄位會使用 NULL 作為不匹配的其他表格中所有欄位的值。

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

Cross Join

也提供一種稱為 CROSS JOIN 的特殊 Join。Cross 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

Self Join

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

通常,表格描述了可以履行彼此之間關係中多個角色的實體。例如,如果您有一個 people 表格,則每一列都可能包含一個 mother 欄位,該欄位引用表格中的其他 people。Self Join 將允許您透過將表格的第二個實例與第一個實例連接起來,在這些值匹配時將這些不同的列縫合在一起。

由於 Self Join 兩次引用相同的表格,因此需要表格別名來消除引用的歧義。例如,在上面的範例中,您可以透過使用別名 people AS childrenpeople AS mothers 來連接 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。PostgreSQL 使用提供的欄位將每個表格中的列縫合在一起。

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

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

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

在這裡,當 table1id 欄位與 table2ident 欄位相符時,table1table2 中的列將被 Join。由於使用了 Inner 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 不指定任何要匹配的欄位。相反,PostgreSQL 將根據每個資料庫中具有匹配欄位的所有欄位自動 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 子句本身中定義它們之間的區別並不總是直觀。

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

例如,假設我們有兩個名為 customerorder 的表格,我們需要將它們 Join 在一起。我們希望透過將 customer.id 欄位與 order.customer_id 欄位相匹配來 Join 這兩個表格。此外,我們對 order 表格中 product_id 為 12345 的列感興趣。

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

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

SELECT
customer.id AS customer_id,
customer.name,
order.id AS order_id,
order.product_id
FROM
customer
LEFT JOIN
order
ON
customer.id = order.customer_id AND order.product_id = 12345;

結果可能看起來像這樣

customer_id | name | order_id | product_id
------------+----------+----------+------------
4380 | Acme Co | 480 | 12345
4380 | Acme Co | 182 | 12345
320 | Other Co | 680 | 12345
4380 | Acme Co | |
320 | Other Co | |
20 | Early Co | |
8033 | Big Co | |
(7 rows)

PostgreSQL 透過執行以下操作得出此結果

  1. customer 表格中的任何列與 order 表格中的列合併,其中
    • customer.idorder.customer_id 相符。
    • order.product_id 與 12345 相符
  2. 由於我們正在使用 Left Join,因此包含來自左側表格(customer)的任何不匹配列,並使用 NULL 值填補來自右側表格(order)的欄位。
  3. 僅顯示 SELECT 欄位規範中列出的欄位。

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

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

SELECT
customer.id AS customer_id,
customer.name,
order.id AS order_id,
order.product_id
FROM
customer
LEFT JOIN
order
ON
customer.id = order.customer_id
WHERE
order.product_id = 12345;

這次,僅顯示三列

customer_id | name | order_id | product_id
------------+----------+----------+------------
4380 | Acme Co | 480 | 12345
4380 | Acme Co | 182 | 12345
320 | Other Co | 680 | 12345
(3 rows)

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

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

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

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

結論

在本指南中,我們涵蓋了聯結 (joins) 如何使關聯式資料庫能夠結合來自不同表格的資料,以提供更有價值的解答。我們討論了 PostgreSQL 支援的各種聯結類型、每種類型如何組裝其結果,以及使用特定種類的聯結時的預期結果。 之後,我們檢視了定義聯結條件的不同方式,並探討了聯結和 WHERE 子句之間的相互作用如何導致意想不到的結果。

聯結是使關聯式資料庫功能強大且足夠靈活以處理如此多不同類型查詢的關鍵部分。 在使用邏輯邊界組織資料的同時,仍然能夠在具體情況下以新穎的方式重新組合資料,這賦予了像 PostgreSQL 這樣的關聯式資料庫令人難以置信的通用性。 學習如何在表格之間執行這種拼接 (stitching) 將使您能夠建立更複雜的查詢,並依靠資料庫來建立資料的完整圖像。

FAQ

是的,PostgreSQL 支援外部聯結。 例如,您可以使用 LEFT OUTER JOIN 或僅使用 LEFT JOIN,如下所示:

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

PostgreSQL 中,LATERAL 關鍵字可以放在子查詢的 FROM 項目之前,並允許子查詢引用出現在 FROM 列表前面項目的欄位。(如果沒有 LATERAL,每個子查詢都會獨立評估,因此無法交叉引用任何其他 FROM 項目。)

是的,可以在 PostgreSQL 中執行 CROSS JOIN。 語法會如下所示:

SELECT select_list
FROM t1
CROSS JOIN t2;

上面的範例會顯示類似這樣的範例輸出

是的,PostgreSQL 支援完整聯結。 它們可以指定為 FULL OUTER JOINFULL JOIN

語法會如下所示:

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

PostgreSQL 中的預設聯結是內部聯結,可以使用 INNER JOIN 或僅使用 JOIN 來指定。

語法是:

SELECT
*
FROM
table_1
[INNER] JOIN table_2
ON table_1.id = table_2.table_1_id;
關於作者
Justin Ellingwood

Justin Ellingwood

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