連線

SQL 中的連線（JOIN）語句用於將資料庫中的兩個或多個表組合起來。由 "連線" 生成的集合，可以被儲存為表，或者當成表來使用。JOIN 語句的含義是把兩張表的屬性透過它們的值組合在一起。基於 ANSI 標準的 SQL 列出了五種 JOIN 方式：內連線（INNER），全外連線（FULL OUTER），左外連線（LEFT OUTER），右外連線（RIGHT OUTER）和交叉連線（CROSS）。在特定的情況下，一張表（基本表，檢視，或連線表）可以和自身進行連線，成為自連線（self-join）。

程式設計師用 JOIN 謂詞表示要得到 "連線" 後的集合。如果求值後謂詞為真，組合後的記錄就會按照預期的方式生成，如一個記錄集，或者一張臨時表。

示例用表

通常會對關係資料庫進行規範化，以消除重複資訊，例如實體型別具有一對多關係時。例如，一個部門可能與許多僱員相關聯。將部門和員工的單獨表連線起來，可以有效地建立另一個表，將兩個表中的資訊組合在一起。

下文中解釋 "連線" 都將用到這裡的兩張表。表中的記錄（行）用於演示不同型別的 "連線" 和 "連線謂詞" 的作用。在下面兩張表中，Department.DepartmentID 是主鍵，Employee.DepartmentID 是外部索引鍵。

注：僱員 "Williams" 不在 部門表中的任何一個部門。同樣，"市場部" 目前沒有員工列出。

這是用來建立上述表的 SQL。

CREATE TABLE department

(

DepartmentID INT Primary key,

DepartmentName VARCHAR (20)

);

CREATE TABLE employee

(LastName VARCHAR (20),

DepartmentID INT references department (DepartmentID)

);

INSERT INTO department VALUES (31, 'Sales');

INSERT INTO department VALUES (33, 'Engineering');

INSERT INTO department VALUES (34, 'Clerical');

INSERT INTO department VALUES (35, 'Marketing');

INSERT INTO employee VALUES ('Rafferty', 31);

INSERT INTO employee VALUES ('Jones', 33);

INSERT INTO employee VALUES ('Heisenberg', 33);

INSERT INTO employee VALUES ('Robinson', 34);

INSERT INTO employee VALUES ('Smith', 34);

INSERT INTO employee VALUES ('Williams', NULL);

交叉連線

交叉連線（cross join），又稱笛卡爾連線（cartesian join）或叉乘（Product），它是所有型別的內連線的基礎。把表視為行記錄的集合，交叉連線即返回這兩個集合的笛卡爾積。 這其實等價於內連線的連結條件為 "永真"，或連線條件不存在。

如果 A 和 B 是兩個集合，它們的交叉連線就記為: A × B。

用於交叉連線的 SQL 程式碼在 FROM 列出表名，但並不包含任何過濾的連線謂詞。

顯式的交叉連線例項:

SELECT FROM employee CROSS JOIN department

隱式的交叉連線例項:

SELECT FROM employee, department;

交叉連線不會應用任何謂詞去過濾結果表中的記錄。程式設計師可以用 WHERE 語句進一步過濾結果集。

在 SQL:2011 標準中，交叉連線是可選的 F401 “Extended joined table” 包的一部分。

通常用於檢查伺服器的效能。

內連線

內連線（inner join）是應用程式中用的普遍的 "連線" 操作，它一般都是預設連線型別。內連線基於連線謂詞將兩張表（如 A 和 B）的列組合在一起，產生新的結果表。查詢會將 A 表的每一行和 B 表的每一行進行比較，並找出滿足連線謂詞的組合。當連線謂詞被滿足，A 和 B 中匹配的行會按列組合（並排組合）成結果集中的一行。連線產生的結果集，可以定義為首先對兩張表做笛卡爾積（交叉連線） -- 將 A 中的每一行和 B 中的每一行組合，然後返回滿足連線謂詞的記錄。實際上 SQL 產品會盡可能用其他方式去實現連線，笛卡爾積運算是非常沒效率的。

SQL 定義了兩種不同語法方式去表示 "連線"。首先是 "顯式連線符號"，它顯式地使用關鍵字 JOIN，其次是 "隱式連線符號"，它使用所謂的 "隱式連線符號"。隱式連線符號把需要連線的表放到 SELECT 語句的 FROM 部分，並用逗號隔開。這樣就構成了一個 "交叉連線"，WHERE 語句可能放置一些過濾謂詞（過濾條件）。那些過濾謂詞在功能上等價於顯式連線符號。SQL 89 標準只支援內部連線與交叉連線，因此只有隱式連線這種表達方式；SQL 92 標準增加了對外部連線的支援，這才有了 JOIN 表示式。

內連線 " 可以進一步被分為：相等連線，自然連線，和交叉連線（見下）。

程式要應該特別注意連線依據的列可能包含 NULL 值，NULL 值不與任何值匹配（甚至和它本身） -- 除非連線條件中顯式地使用 IS NULL 或 IS NOT NULL 等謂詞。

例如，下面的查詢透過 Employee 表和 Department 表共有的屬性 DepartmentID 連線兩表。在兩表 DepartmentID 匹配之處（如連線謂詞被滿足），查詢將組合兩表的 LastName，DepartmentID 和 DepartmentName 等列，把它們放到結果表的一行（一條記錄）裡。當 DepartmentID 不匹配，就不會往結果表中生成任何資料。

顯式的內連線例項:

SELECT FROM employee

INNER JOIN department

ON employee.DepartmentID = department.DepartmentID

等價於:

SELECT FROM employee, department

WHERE employee.DepartmentID = department.DepartmentID

顯式的內連線的輸出結果:

注 僱員 "Williams" 和部門 "市場部" 都未出現。它們在預期得到的表中沒有任何匹配的記錄: "Williams" 沒有關聯的部門，而號碼為 35 的部門中沒有任何僱員。這樣，在 "連線" 後的表中，就沒有關於 Williams 或 市場部 的資訊了。相對於預期的結果，這個行為可能是一個微妙的 Bug。外連線可能可以避免這種情況。

3.1 相等連結

相等連線 （equi-join，或 equijoin），是比較連線（θ 連線）的一種特例，它的連線謂詞只用了相等比較。使用其他比較運算子（如 ）的不是相等連線。前面的查詢已經展示了一個相等連線的例項:

SELECT FROM employee JOIN department

ON employee.DepartmentID = department.DepartmentID

SQL-92 提供了一種可選的簡短符號去表達相等連線，它使用 USING 關鍵字 （Feature ID F402）:

SELECT FROM employee INNER JOIN department

USING (DepartmentID)

USING 結構並不僅僅是語法糖，上面查詢的結果和使用顯式謂詞得到的查詢得到的結果是不同的。特別地，在 USING 部分列出的列（column）將在連線結果的臨時表中只出現一次，且無表名限定列名。在上面的例子中，連線結果的臨時表產生單獨的名為 DepartmentID 的列，而不是 employee.DepartmentID 或 department.DepartmentID。

MS SQL Server 和 Sybase 不支援 USING 語句。

3.2 自然連線

自然連線比相等連線的進一步特例化。兩表做自然連線時，兩表中的所有名稱相同的列都將被比較，這是隱式的。自然連線得到的結果表中，兩表中名稱相同的列只出現一次。

上面用於內連線的查詢例項可以用自然連線的方式表示如下:

SELECT FROM employee NATURAL JOIN department

用了 USING 語句後，在連線表中，DepartmentID 列只出現一次，且沒有表名作字首:

在 Oracle 裡用 JOIN USING 或 NATURAL JOIN 時，如果兩表共有的列的名稱前加上某表名作為字首，則會報編譯錯誤: "ORA-25154: column part of USING clause cannot have qualifier" 或 "ORA-25155: column used in NATURAL join cannot have qualifier"。

外連線

外連線並不要求連線的兩表的每一條記錄在對方表中都一條匹配的記錄。要保留所有記錄（甚至這條記錄沒有匹配的記錄也要保留）的表稱為保留表。 外連線可依據連線表保留左表，右表或全部表的行而進一步分為左外連線，右外連線和全連線。

（在這種情況下 left 和 right 表示 JOIN 關鍵字的兩邊。）

在標準的 SQL 語言中，外連線沒有隱式的連線符號。

外部連線既包含 ON 子句又包含 WHERE 子句時，應當只把表之間的連線條件寫在 ON 子句中，對錶中資料的篩選必須寫在 WHERE 子句中。而內部連線的各條件表示式既可以放在 ON 子句又可以放在 WHERE 子句中。這是因為對於外部連線，保留表中被 ON 子句篩除掉的行要被添加回來，在此操作之後才會用 WHERE 子句去篩選連線結果中的各行。

4.1 左外連線

左外連線（left outer join），亦簡稱為左連線（left join），若 A 和 B 兩表進行左外連線，那麼結果表中將包含 "左表"（即表 A）的所有記錄，即使那些記錄在 "右表" B 沒有符合連線條件的匹配。這意味著即使 ON 語句在 B 中的匹配項是 0 條，連線操作還是會返回一條記錄，只不過這條記錄中來自於 B 的每一列的值都為 NULL。這意味著左外連線會返回左表的所有記錄和右表中匹配記錄的組合（如果右表中無匹配記錄，來自於右表的所有列的值設為 NULL）。如果左表的一行在右表中存在多個匹配行，那麼左表的行會複製和右表匹配行一樣的數量，並進行組合生成連線結果。

如，這允許我們去找到僱員的部門時，顯示所有僱員，即使這個僱員還沒有關聯的部門。（在上面的內連線部分由一個相反的例子，沒有關聯的部門號的僱員在結果中是不顯示的）。

左外連線例項:

（相對於內連線增添的行用斜體標出）

SELECT FROM employee LEFT OUTER JOIN department

ON employee.DepartmentID = department.DepartmentID;

4.2 右外連線

表示表 A 和表 B 之間的右連線 SQL 語句的維恩圖。

右外連線，亦簡稱右連線，它與左外連線完全類似，只不過是作連線的表的順序相反而已。如果 A 表右連線 B 表，那麼 "右表" B 中的每一行在連線表中至少會出現一次。如果 B 表的記錄在 "左表" A 中未找到匹配行，連線表中來源於 A 的列的值設為 NULL。

右連線操作返回右表的所有行和這些行在左表中匹配的行（沒有匹配的，來源於左表的列值設為 NULL）。

例如，這允許我們在找每一個僱員以及他的部門資訊時，當這個部門裡沒有任何僱員時，也把部門顯示出來。

右連線的例項:

（相對於內連線增添的行用斜體標出）

SELECT FROM employee RIGHT OUTER JOIN department

ON employee.DepartmentID = department.DepartmentID

實際上顯式的右連線很少使用，因為它總是可以被替換成左連線 -- 換換表的位置就可以了，另外，右連線相對於左連線並沒有什麼額外的功能。上表同樣可以使用左連線得到:

SELECT FROM department LEFT OUTER JOIN employee

ON employee.DepartmentID = department.DepartmentID

4.3 全連線

全連線是左右外連線的並集。連線表包含被連線的表的所有記錄，如果缺少匹配的記錄，即以 NULL 填充。

如，這允許我們檢視每一個在部門裡的員工和每一個擁有僱員的部門，同時，還能看到不在任何部門的員工以及沒有任何員工的部門。

全連線例項:

SELECT FROM employee

FULL OUTER JOIN department

ON employee.DepartmentID = department.DepartmentID

一些資料庫系統（如 MySQL）並不直接支援全連線，但它們可以透過左右外連線的並集（參: union）來模擬實現。和上面等價的例項:

SELECT FROM employee

LEFT JOIN department

ON employee.DepartmentID = department.DepartmentID

UNION

SELECT FROM employee

RIGHT JOIN department

ON employee.DepartmentID = department.DepartmentID

WHERE employee.DepartmentID IS NULL

SQLite 不支援右連線，全外連線可以按照下面的方式模擬:

SELECT employee.*, department.FROM employee

LEFT JOIN department

ON employee.DepartmentID = department.DepartmentID

UNION

SELECT employee.*, department.FROM department

LEFT JOIN employee

ON employee.DepartmentID = department.DepartmentID

WHERE employee.DepartmentID IS NULL

自連線

自連線就是和自身連線。 下面的例子是一個很好的說明。

5.1 示例

構建一個查詢，它試圖找到這樣的記錄：每條記錄包含兩個僱員，他們來自於同一個國家。如果你有兩張僱員表（Employee），那麼只要第一張表的僱員和第二張表的僱員在同樣的國家的就行了，你可以用一個通常的連線（相等連線）操作去得到這個表。不過，這裡所有僱員資訊都在一張單獨的大表裡。

下面一個修改過的僱員表 Employee:

示例解決方案的查詢可以寫成如下:

SELECT F.EmployeeID, F.LastName, S.EmployeeID, S.LastName, F.Country

FROM Employee F, Employee S

WHERE F.Country = S.Country

AND F.EmployeeID

它執行後將生成下面的表:

關於這個例子，請注意:

F 和 S 是僱員表（employee）的第一個和第二個複製的別名
條件 F.Country = S.Country 排除了在不同國家的僱員的組合。這個例子僅僅期望得到在相同國家的僱員的組合。
條件 F.EmployeeID 排除了僱員號（EmployeeID）相同的組合。
F.EmployeeID 排除了重複的組合。沒有這個條件的話，將生成類似下面表中的無用資料（僅以 Germany 為例）

只有當中的兩行滿足最初問題的要求，第一項和最後一項對於本例來講毫無用處。

替代方式

外連線查詢得到的結果也可以透過關聯子查詢得到。

例如

SELECT employee.LastName, employee.DepartmentID, department.DepartmentName

FROM employee LEFT OUTER JOIN department

ON employee.DepartmentID = department.DepartmentID

也可以寫成如下樣子:

SELECT employee.LastName, employee.DepartmentID,

(SELECT department.DepartmentName

FROM department

WHERE employee.DepartmentID = department.DepartmentID )

FROM employee

實現

7.1 連線演算法

執行一個連線操作，存在三種基本的演算法。

7.1.1 巢狀迴圈（LOOP JOIN）

巢狀迴圈類似於 C 語言程式設計時的雙重迴圈。作為外層迴圈逐行掃描的表，稱為外部輸入表；針對外部輸入表的每一行，要逐行掃描檢查匹配的另一張表，稱為內部輸入表（相當於內層迴圈）。適用於外部輸入表的行數較少，內部輸入表建立了索引的情形。

7.1.2 合併連線（MERGE JOIN）

類似於兩個有序陣列的合併。兩個輸入表都在合併列上排序；然後依序對兩張表逐行做連線或捨棄。如果預先建好了索引，合併連線的計算複雜度是線性的。

7.1.3 雜湊連線（HASH JOIN）

適用於查詢的中間結果，通常是無索引的臨時表；以及中間結果的行數很大時。雜湊連線選擇行數較小的輸入表作為生成輸入，對其連線列值應用雜湊函式，把其行（的儲存位置）放入雜湊桶中。