簡介
將相關資料拆分到不同的表中,從一致性、靈活性和某些型別的效能角度來看是有益的。但是,當相關資訊跨越多個表時,您仍然需要一種合理的方式來重新整合記錄。
在關係型資料庫中,連線(joins)提供了一種基於公共欄位值組合兩個或多個表中記錄的方式。不同型別的連線可以根據如何處理不匹配的行來達到不同的結果。在本指南中,我們將討論 PostgreSQL 提供的各種連線型別,以及如何使用它們來組合來自多個源的表資料。
什麼是連線?
簡而言之,連線(joins)是一種顯示來自多個表資料的方式。它們透過基於某些列中的匹配值,將不同來源的記錄拼接在一起。每個結果行都包含來自第一個表的一條記錄,與來自第二個表的一行組合而成,基於每個表中的一個或多個列具有相同的值。
連線的基本語法如下:
SELECT*FROM<first_table><join_type> <second_table><join_condition>;
在連線中,每個結果行都由第一個表的所有列以及第二個表的所有列構成。SELECT 查詢部分可以用來指定您希望顯示的精確列。
如果用於比較的列中的值不唯一,則可以從原始表中構造多行。例如,假設您從第一個表比較的列中有兩條記錄的值為“red”。與此匹配的是第二個表中的一列,該列有三行具有該值。連線將為該值生成六個不同的行,表示可以實現的不同組合。
連線的型別和連線條件決定了每個顯示行的構造方式。這會影響每個表中那些有匹配或沒有匹配連線條件的行的處理方式。
為了方便起見,許多連線將一個表的主鍵與第二個表中的相關外部索引鍵進行匹配。儘管主鍵和外部索引鍵僅由資料庫系統用於維護一致性保證,但它們的關係通常使其成為連線條件的好選擇。
不同型別的連線
有多種型別的連線可用,每種都可能產生不同的結果。瞭解每種型別的構造方式將有助於您確定哪種型別適合不同的場景。
內連線
預設的連線稱為內連線(inner join)。在 PostgreSQL 中,這可以透過使用INNER JOIN或簡單地使用JOIN來指定。
下面是一個演示內連線語法的典型示例:
SELECT*FROMtable_1[INNER] JOIN table_2ON table_1.id = table_2.table_1_id;
內連線是最嚴格的連線型別,因為它只顯示由每個表中的行組合建立的行。構成表中任何在另一個表中沒有匹配對應項的行都將從結果中刪除。例如,如果第一個表在比較列中有一個值“blue”,而第二個表中沒有具有該值的記錄,則該行將從輸出中刪除。
如果您將結果表示為元件表的維恩圖,內連線允許您表示兩個圓的重疊區域。只存在於其中一個表中的值都不會顯示。
左連線
左連線是一種連線,它顯示內連線中找到的所有記錄,以及來自第一個表的所有不匹配行。在 PostgreSQL 中,這可以指定為LEFT OUTER JOIN或僅為LEFT JOIN。
左連線的基本語法遵循以下模式:
SELECT*FROMtable_1LEFT JOIN table_2ON table_1.id = table_2.table_1_id;
左連線首先執行內連線以從兩個表中所有匹配的記錄構建行。然後,第一個表中不匹配的記錄也會被包含進來。由於連線中的每一行都包含兩個表的列,不匹配的列將使用NULL作為第二個表中所有列的值。
如果您將結果表示為元件表的維恩圖,左連線允許您表示整個左圓。左圓中由兩個圓的交集表示的部分將有右表補充的額外資料。
右連線
右連線是一種連線,它顯示內連線中找到的所有記錄,以及來自第二個表的所有不匹配行。在 PostgreSQL 中,這可以指定為RIGHT OUTER JOIN或僅為RIGHT JOIN。
右連線的基本語法遵循以下模式:
SELECT*FROMtable_1RIGHT JOIN table_2ON table_1.id = table_2.table_1_id;
右連線首先執行內連線以從兩個表中所有匹配的記錄構建行。然後,第二個表中不匹配的記錄也會被包含進來。由於連線中的每一行都包含兩個表的列,不匹配的列將使用NULL作為第一個表中所有列的值。
如果您將結果表示為元件表的維恩圖,右連線允許您表示整個右圓。右圓中由兩個圓的交集表示的部分將有左表補充的額外資料。
全連線
全連線是一種連線,它顯示內連線中找到的所有記錄,以及來自兩個元件表的所有不匹配行。在 PostgreSQL 中,這可以指定為FULL OUTER JOIN或僅為FULL JOIN。
全連線的基本語法遵循以下模式:
SELECT*FROMtable_1FULL JOIN table_2ON table_1.id = table_2.table_1_id;
全連線首先執行內連線以從兩個表中所有匹配的記錄構建行。然後,兩個表中不匹配的記錄也會被包含進來。由於連線中的每一行都包含兩個表的列,不匹配的列將使用NULL作為不匹配的另一個表中所有列的值。
如果您將結果表示為元件表的維恩圖,全連線允許您完全表示兩個元件圓。兩個圓的交集將包含由每個元件表提供的值。圓中超出重疊區域的部分將包含它們所屬表的值,並使用NULL填充另一個表中找到的列。
交叉連線
還提供了一種特殊的連線,稱為CROSS JOIN。交叉連線不使用任何比較來確定每個表中的行是否相互匹配。相反,結果是透過簡單地將第一個表中的每一行新增到第二個表中的每一行來構造的。
這會生成兩個或多個表中行的笛卡爾積。實際上,這種連線方式無條件地組合了每個表中的行。因此,如果每個表有三行,則結果表將有九行,包含兩個表中的所有列。
例如,如果您有一個名為t1的表與一個名為t2的表組合,每個表都有行r1、r2和r3,則結果將是九行,組合如下:
t1.r1 + t2.r1t1.r1 + t2.r2t1.r1 + t2.r3t1.r2 + t2.r1t1.r2 + t2.r2t1.r2 + t2.r3t1.r3 + t2.r1t1.r3 + t2.r2t1.r3 + t2.r3
自連線
自連線是任何將表的行與其自身結合的連線。這可能不會立即顯現其用途,但它實際上有許多常見的應用。
通常,表描述了可以相互扮演多種角色的實體。例如,如果您有一個people表,每行可能包含一個mother列,該列引用表中的其他people。自連線將允許您透過將表的第二個例項連線到第一個例項來拼接這些不同的行,其中這些值匹配。
由於自連線兩次引用同一個表,因此需要表別名來消除引用歧義。例如,在上面的示例中,您可以使用別名people AS children和people AS mothers來連線people表的兩個例項。這樣,您在定義連線條件時就可以指定所引用的表例項。
這是另一個例子,這次代表員工和經理之間的關係:
SELECT*FROMpeople AS employeeJOIN people AS managerON employee.manager_id = manager.id;
連線條件
在組合表時,連線條件決定了行將如何匹配以形成複合結果。基本前提是定義每個表中必須匹配的列,以便在該行上發生連線。
ON 子句
定義表連線條件最標準的方法是使用ON子句。ON子句使用等號來指定將要比較的每個表中的精確列,以確定何時可以發生連線。PostgreSQL 使用提供的列來拼接每個表中的行。
ON子句是最冗長的,但也是可用連線條件中最靈活的。它允許無論組合的每個表的列名標準化程度如何,都能保持特異性。
ON 子句的基本語法如下:
SELECT*FROMtable1JOINtable2ONtable1.id = table2.ident;
在這裡,當table1的id列與table2的ident列匹配時,table1和table2的行將被連線。由於使用了內連線,結果只會顯示那些已連線的行。由於查詢使用了萬用字元*字元,因此將顯示兩個表中的所有列。
這意味著table1的id列和table2的ident列都將顯示,儘管它們透過滿足連線條件而具有完全相同的值。您可以透過在SELECT列列表中明確指定要顯示的列來避免這種重複。
USING 子句
USING子句是指定ON子句條件的簡寫,當被比較的列在兩個表中具有相同的名稱時可以使用。USING子句接受一個用括號括起來的列表,其中包含應被比較的共享列名。
USING子句的一般語法採用這種格式:
SELECT*FROMtable1JOINtable2USING(id, state);
當兩個表共享的兩個列(id和state)的值都匹配時,此連線將table1與table2組合。
同樣的連線可以用ON更冗長地表達如下:
SELECT*FROMtable1JOINtable2ONtable1.id = table2.id AND table1.state = table2.state;
雖然上述兩種連線都會產生相同的資料,但它們的顯示方式略有不同。雖然ON子句包含兩個表中的所有列,但USING子句會抑制重複的列。因此,結果中將只有共享列的單個例項(而不是每個表有兩個單獨的id列和兩個單獨的state列),然後是table1和table2提供的所有其他列。
NATURAL 子句
NATURAL子句是另一種簡寫,可以進一步減少USING子句的冗長。一個NATURAL連線不指定任何要匹配的列。相反,PostgreSQL 會根據每個資料庫中所有具有匹配列的列自動連線表。
NATURAL連線子句的一般語法如下:
SELECT*FROMtable1NATURAL JOINtable2;
假設table1和table2都具有名為id、state和company的列,則上述查詢等效於使用ON子句的此查詢:
SELECT*FROMtable1JOINtable2ONtable1.id = table2.id AND table1.state = table2.state AND table1.company = table2.company;
以及使用USING子句的此查詢:
SELECT*FROMtable1JOINtable2USING(id, state, company);
與USING子句一樣,NATURAL子句會抑制重複列,因此結果中每個連線的列只有一個例項。
雖然NATURAL子句可以減少查詢的冗長,但在使用它時必須小心。由於用於連線表的列是自動計算的,如果元件表中的列發生變化,結果可能會因新的連線條件而大相徑庭。
連線條件和WHERE子句
連線條件與使用WHERE子句過濾資料行所使用的比較有許多共同之處。兩種結構都定義了必須求值為真的表示式才能考慮該行。因此,在WHERE結構中包含額外比較與在連線子句本身中定義它們之間的區別並不總是直觀的。
為了理解將產生的差異,我們必須檢視 PostgreSQL 處理查詢不同部分的順序。在這種情況下,連線條件中的謂詞首先被處理,以在記憶體中構建虛擬連線表。在此階段之後,WHERE子句中的表示式將被評估以過濾結果行。
例如,假設我們有兩個表,分別叫做customer和order,我們需要將它們連線起來。我們希望透過匹配customer.id列和order.customer_id列來連線這兩個表。此外,我們對order表中product_id為 12345 的行感興趣。
根據上述要求,我們有兩個關心條件。然而,我們表達這些條件的方式將決定我們收到的結果。
首先,讓我們將兩者都用作LEFT JOIN的連線條件:
SELECTcustomer.id AS customer_id,customer.name,order.id AS order_id,order.product_idFROMcustomerLEFT JOINorderONcustomer.id = order.customer_id AND order.product_id = 12345;
結果可能看起來像這樣:
customer_id | name | order_id | product_id------------+----------+----------+------------4380 | Acme Co | 480 | 123454380 | Acme Co | 182 | 12345320 | Other Co | 680 | 123454380 | Acme Co | |320 | Other Co | |20 | Early Co | |8033 | Big Co | |(7 rows)
PostgreSQL 透過執行以下操作得出此結果:
- 將
customer表中的行與order表中的行組合,條件是:customer.id匹配order.customer_id。order.product_id匹配 12345。
- 因為我們使用的是左連線,所以包含來自左表(
customer)的任何不匹配行,並用NULL值填充右表(order)的列。 - 僅顯示
SELECT列規範中列出的列。
結果是所有連線的行都符合我們正在尋找的兩個條件。然而,左連線導致 PostgreSQL 也包含了第一個表中不滿足連線條件的行。這導致“剩餘”的行似乎不符合查詢的明顯意圖。
如果我們將第二個查詢(order.product_id = 12345)移到WHERE子句,而不是將其作為連線條件,我們將得到不同的結果:
SELECTcustomer.id AS customer_id,customer.name,order.id AS order_id,order.product_idFROMcustomerLEFT JOINorderONcustomer.id = order.customer_idWHEREorder.product_id = 12345;
這次,只顯示了三行:
customer_id | name | order_id | product_id------------+----------+----------+------------4380 | Acme Co | 480 | 123454380 | Acme Co | 182 | 12345320 | Other Co | 680 | 12345(3 rows)
比較執行的順序是造成這些差異的原因。這次,PostgreSQL 像這樣處理查詢:
- 將
customer表中的任何行與order表中的行組合,條件是customer.id匹配order.customer_id。 - 因為我們使用的是左連線,所以包含來自左表(
customer)的任何不匹配行,並用NULL值填充右表(order)的列。 - 評估
WHERE子句,以刪除order.product_id列值不為 12345 的任何行。 - 僅顯示
SELECT列規範中列出的列。
這次,即使我們使用的是左連線,WHERE子句也會透過過濾掉所有沒有正確product_id的行來截斷結果。因為任何不匹配的行都將product_id設定為NULL,這會刪除所有由左連線填充的不匹配行。它還會刪除任何透過連線條件匹配但未透過第二輪檢查的行。
瞭解 PostgreSQL 執行查詢的基本過程可以幫助您避免在處理資料時一些容易犯但難以除錯的錯誤。
結論
在本指南中,我們介紹了連線如何使關係型資料庫能夠組合來自不同表的資料以提供更有價值的答案。我們討論了 PostgreSQL 支援的各種連線、每種型別如何組合其結果以及在使用特定型別連線時應期待什麼。之後,我們回顧了定義連線條件的不同方式,並探討了連線與WHERE子句之間的相互作用如何導致意外結果。
連線是關係型資料庫強大且足夠靈活以處理如此多不同型別查詢的重要組成部分。使用邏輯邊界組織資料,同時仍能根據具體情況以新穎的方式重新組合資料,這賦予了 PostgreSQL 等關係型資料庫令人難以置信的多功能性。學習如何在表之間進行這種拼接將使您能夠建立更復雜的查詢,並依靠資料庫建立完整的資料圖景。
常見問題
是的,PostgreSQL 支援外連線。例如,您可以使用LEFT OUTER JOIN或只使用LEFT JOIN,如下所示:
SELECT*FROMtable_1LEFT JOIN table_2ON table_1.id = table_2.table_1_id;
PostgreSQL中的LATERAL關鍵字可以置於子SELECT FROM項之前,允許子SELECT引用FROM列表中在其之前出現的FROM項的列。(如果沒有 LATERAL,每個子SELECT都是獨立評估的,因此不能交叉引用任何其他 FROM 項。)
是的,PostgreSQL 支援全連線。它們可以指定為FULL OUTER JOIN或FULL JOIN。
語法會像這樣:
SELECT*FROMtable_1FULL JOIN table_2ON table_1.id = table_2.table_1_id;
PostgreSQL 中的預設連線是內連線,可以透過使用INNER JOIN或僅使用JOIN來指定。
語法是:
SELECT*FROMtable_1[INNER] JOIN table_2ON table_1.id = table_2.table_1_id;
