本文介紹什麼是 SQL INNER JOIN 聯結,為什麼使用聯結,如何編寫使用聯結的 SELECT 語句。並講述了一些關係資料庫設計的基本知識,包括等值聯結(也稱為內聯結)這種最常用的聯結。 一、聯結 SQL 最強大的功能之一就是能在數據查詢的執行中聯結(join)表。聯結是利用 SQL 的 SE ...
目錄
本文介紹什麼是 SQL INNER JOIN
聯結,為什麼使用聯結,如何編寫使用聯結的 SELECT
語句。並講述了一些關係資料庫設計的基本知識,包括等值聯結(也稱為內聯結)這種最常用的聯結。
一、聯結
SQL 最強大的功能之一就是能在數據查詢的執行中聯結(join)表。聯結是利用 SQL 的 SELECT
能執行的最重要的操作,很好地理解聯結及其語法是學習 SQL 的極為重要的部分。
在能夠有效地使用聯結前,必須瞭解關係表以及關係資料庫設計的一些基礎知識。下麵的介紹並不能涵蓋這一主題的所有內容,但作為入門已經夠了。
1.1 關係表
理解關係表,最好是來看個例子。
有一個包含產品目錄的資料庫表,其中每類物品占一行。對於每一種物品,要存儲的信息包括產品描述、價格,以及生產該產品的供應商。
現在有同一供應商生產的多種物品,那麼在何處存儲供應商名、地址、聯繫方法等供應商信息呢?將這些數據與產品信息分開存儲的理由是:
- 同一供應商生產的每個產品,其供應商信息都是相同的,對每個產品重覆此信息既浪費時間又浪費存儲空間;
- 如果供應商信息發生變化,例如供應商遷址或電話號碼變動,只需修改一次即可;
- 如果有重覆數據(即每種產品都存儲供應商信息),則很難保證每次輸入該數據的方式都相同。不一致的數據在報表中就很難利用。
關鍵是,相同的數據出現多次決不是一件好事,這是關係資料庫設計的基礎。
關係表的設計就是要把信息分解成多個表,一類數據一個表。各表通過某些共同的值互相關聯(所以才叫關係資料庫)。
在這個例子中可建立兩個表:一個存儲供應商信息,另一個存儲產品信息。Vendors
表包含所有供應商信息,每個供應商占一行,具有唯一的標識。
此標識稱為主鍵(primary key),可以是供應商 ID
或任何其他唯一值。
Products
表只存儲產品信息,除了存儲供應商 ID
(Vendors
表的主鍵)外,它不存儲其他有關供應商的信息。
Vendors
表的主鍵將 Vendors
表與 Products
表關聯,利用供應商 ID
能從 Vendors
表中找出相應供應商的詳細信息。
這樣做的好處是:
- 供應商信息不重覆,不會浪費時間和空間;
- 如果供應商信息變動,可以只更新
Vendors
表中的單個記錄,相關表中的數據不用改動; - 由於數據不重覆,數據顯然是一致的,使得處理數據和生成報表更簡單。
總之,關係數據可以有效地存儲,方便地處理。因此,關係資料庫的可伸縮性遠比非關係資料庫要好。
可伸縮(scale)
能夠適應不斷增加的工作量而不失敗。設計良好的資料庫或應用程式稱為可伸縮性好(scale well)。
1.2 為什麼使用聯結
如前所述,將數據分解為多個表能更有效地存儲,更方便地處理,並且可伸縮性更好。但這些好處是有代價的。
如果數據存儲在多個表中,怎樣用一條 SELECT
語句就檢索出數據呢?
答案是使用聯結。簡單說,聯結是一種機制,用來在一條 SELECT
語句中關聯表,因此稱為聯結。使用特殊的語法,可以聯結多個表返回一組輸出,聯結在運行時關聯表中正確的行。
說明:使用互動式 DBMS 工具
重要的是,要理解聯結不是物理實體。換句話說,它在實際的資料庫表中並不存在。DBMS 會根據需要建立聯結,它在查詢執行期間一直存在。
許多 DBMS 提供圖形界面,用來互動式地定義表關係。這些工具極其有助於維護引用完整性。在使用關係表時,僅在關係列中插入合法數據是非常重要的。
回到這裡的例子,如果
Products
表中存儲了無效的供應商ID
,則相應的產品不可訪問,因為它們沒有關聯到某個供應商。為避免這種情況發生,可指示資料庫只允許在
Products
表的供應商ID
列中出現合法值(即出現在Vendors
表中的供應商)。引用完整性表示 DBMS 強制實施數據完整性規則。這些規則一般由提供了界面的 DBMS 管理。
二、創建聯結
創建聯結非常簡單,指定要聯結的所有表以及關聯它們的方式即可。請看下麵的例子:
SELECT vend_name, prod_name, prod_price
FROM Vendors, Products
WHERE Vendors.vend_id = Products.vend_id;
輸出:
vend_name prod_name prod_price
-------------------- -------------------- ----------
Doll House Inc. Fish bean bag toy 3.4900
Doll House Inc. Bird bean bag toy 3.4900
Doll House Inc. Rabbit bean bag toy 3.4900
Bears R Us 8 inch teddy bear 5.9900
Bears R Us 12 inch teddy bear 8.9900
Bears R Us 18 inch teddy bear 11.9900
Doll House Inc. Raggedy Ann 4.9900
Fun and Games King doll 9.4900
Fun and Games Queen doll 9.4900
我們來看這段代碼。
SELECT
語句與前面所有語句一樣指定要檢索的列。這裡最大的差別是所指定的兩列(prod_name
和 prod_price
)在一個表中,而第一列(vend_name
)在另一個表中。
現在來看 FROM
子句。
與以前的 SELECT
語句不一樣,這條語句的 FROM
子句列出了兩個表:Vendors
和 Products
。
它們就是這條 SELECT
語句聯結的兩個表的名字。這兩個表用 WHERE
子句正確地聯結,WHERE
子句指示 DBMS 將 Vendors
表中的 vend_id
與 Products
表中的 vend_id
匹配起來。
可以看到,要匹配的兩列指定為 Vendors.vend_id
和 Products.vend_id
。這裡需要這種完全限定列名,如果只給出 vend_id
,DBMS 就不知道指的是哪一個(每個表中有一個)。
從前面的輸出可以看到,一條 SELECT
語句返回了兩個不同表中的數據。
警告:完全限定列名
就像 如何在 SQL 中使用子查詢 提到的,在引用的列可能出現歧義時,必須使用完全限定列名(用一個句點分隔表名和列名)。
如果引用一個沒有用表名限制的具有歧義的列名,大多數 DBMS 會返回錯誤。
2.1 WHERE 子句的重要性
使用 WHERE
子句建立聯結關係似乎有點奇怪,但實際上是有個很充分的理由的。
要記住,在一條 SELECT
語句中聯結幾個表時,相應的關係是在運行中構造的。在資料庫表的定義中沒有指示 DBMS 如何對錶進行聯結的內容。你必須自己做這件事情。
在聯結兩個表時,實際要做的是將第一個表中的每一行與第二個表中的每一行配對。
WHERE
子句作為過濾條件,只包含那些匹配給定條件(這裡是聯結條件)的行。沒有 WHERE
子句,第一個表中的每一行將與第二個表中的每一行配對,而不管它們邏輯上是否能配在一起。
笛卡兒積(cartesian product)
由沒有聯結條件的表關係返回的結果為笛卡兒積。檢索出的行的數目將是第一個表中的行數乘以第二個表中的行數。
理解這一點,請看下麵的 SELECT
語句及其輸出:
SELECT vend_name, prod_name, prod_price
FROM Vendors, Products;
輸出:
vend_name prod_name prod_price
---------------- ---------------------------- ----------
Bears R Us 8 inch teddy bear 5.99
Bears R Us 12 inch teddy bear 8.99
Bears R Us 18 inch teddy bear 11.99
Bears R Us Fish bean bag toy 3.49
Bears R Us Bird bean bag toy 3.49
Bears R Us Rabbit bean bag toy 3.49
Bears R Us Raggedy Ann 4.99
Bears R Us King doll 9.49
Bears R Us Queen doll 9.49
Bear Emporium 8 inch teddy bear 5.99
Bear Emporium 12 inch teddy bear 8.99
Bear Emporium 18 inch teddy bear 11.99
Bear Emporium Fish bean bag toy 3.49
Bear Emporium Bird bean bag toy 3.49
Bear Emporium Rabbit bean bag toy 3.49
Bear Emporium Raggedy Ann 4.99
Bear Emporium King doll 9.49
Bear Emporium Queen doll 9.49
Doll House Inc. 8 inch teddy bear 5.99
Doll House Inc. 12 inch teddy bear 8.99
Doll House Inc. 18 inch teddy bear 11.99
Doll House Inc. Fish bean bag toy 3.49
Doll House Inc. Bird bean bag toy 3.49
Doll House Inc. Rabbit bean bag toy 3.49
Doll House Inc. Raggedy Ann 4.99
Doll House Inc. King doll 9.49
Doll House Inc. Queen doll 9.49
Furball Inc. 8 inch teddy bear 5.99
Furball Inc. 12 inch teddy bear 8.99
Furball Inc. 18 inch teddy bear 11.99
Furball Inc. Fish bean bag toy 3.49
Furball Inc. Bird bean bag toy 3.49
Furball Inc. Rabbit bean bag toy 3.49
Furball Inc. Raggedy Ann 4.99
Furball Inc. King doll 9.49
Furball Inc. Queen doll 9.49
Fun and Games 8 inch teddy bear 5.99
Fun and Games 12 inch teddy bear 8.99
Fun and Games 18 inch teddy bear 11.99
Fun and Games Fish bean bag toy 3.49
Fun and Games Bird bean bag toy 3.49
Fun and Games Rabbit bean bag toy 3.49
Fun and Games Raggedy Ann 4.99
Fun and Games King doll 9.49
Fun and Games Queen doll 9.49
Jouets et ours 8 inch teddy bear 5.99
Jouets et ours 12 inch teddy bear 8.99
Jouets et ours 18 inch teddy bear 11.99
Jouets et ours Fish bean bag toy 3.49
Jouets et ours Bird bean bag toy 3.49
Jouets et ours Rabbit bean bag toy 3.49
Jouets et ours Raggedy Ann 4.99
Jouets et ours King doll 9.49
Jouets et ours Queen doll 9.49
從上面的輸出可以看到,相應的笛卡兒積不是我們想要的。這裡返回的數據用每個供應商匹配了每個產品,包括了供應商不正確的產品(即使供應商根本就沒有產品)。
註意:不要忘了
WHERE
子句要保證所有聯結都有
WHERE
子句,否則 DBMS 將返回比想要的數據多得多的數據。同理,要保證
WHERE
子句的正確性。不正確的過濾條件會導致 DBMS 返回不正確的數據。
提示:叉聯結
有時,返回笛卡兒積的聯結,也稱叉聯結(cross join)。
2.2 內聯結
目前為止使用的聯結稱為等值聯結(equijoin),它基於兩個表之間的相等測試。這種聯結也稱為內聯結(inner join)。其實,可以對這種聯結使用稍微不同的語法,明確指定聯結的類型。
下麵的 SELECT
語句返回與前面例子完全相同的數據:
SELECT vend_name, prod_name, prod_price
FROM Vendors
INNER JOIN Products ON Vendors.vend_id = Products.vend_id;
此語句中的 SELECT
與前面的 SELECT
語句相同,但 FROM
子句不同。
這裡,兩個表之間的關係是以 INNER JOIN
指定的部分 FROM
子句。
在使用這種語法時,聯結條件用特定的 ON
子句而不是 WHERE
子句給出。傳遞給 ON
的實際條件與傳遞給 WHERE
的相同。
至於選用哪種語法,請參閱具體的 DBMS 文檔。
說明:“正確的”語法
ANSI SQL 規範首選
INNER JOIN
語法,之前使用的是簡單的等值語法。其實,SQL 語言純正論者是用鄙視的眼光看待簡單語法的。
這就是說,DBMS 的確支持簡單格式和標準格式,我建議你要理解這兩種格式,具體使用就看你用哪個更順手了。
2.3 聯結多個表
SQL 不限制一條 SELECT
語句中可以聯結的表的數目。創建聯結的基本規則也相同。首先列出所有表,然後定義表之間的關係。例如:
SELECT prod_name, vend_name, prod_price, quantity
FROM OrderItems, Products, Vendors
WHERE Products.vend_id = Vendors.vend_id
AND OrderItems.prod_id = Products.prod_id
AND order_num = 20007;
輸出:
prod_name vend_name prod_price quantity
--------------- ------------- ---------- --------
18 inch teddy bear Bears R Us 11.9900 50
Fish bean bag toy Doll House Inc. 3.4900 100
Bird bean bag toy Doll House Inc. 3.4900 100
Rabbit bean bag toy Doll House Inc. 3.4900 100
Raggedy Ann Doll House Inc. 4.9900 50
這個例子顯示訂單 20007
中的物品。訂單物品存儲在 OrderItems
表中。每個產品按其產品 ID
存儲,它引用 Products
表中的產品。
這些產品通過供應商 ID
聯結到 Vendors
表中相應的供應商,供應商 ID
存儲在每個產品的記錄中。
這裡的 FROM
子句列出三個表,WHERE
子句定義這兩個聯結條件,而第三個聯結條件用來過濾出訂單 20007
中的物品。
註意:性能考慮
DBMS 在運行時關聯指定的每個表,以處理聯結。這種處理可能非常耗費資源,因此應該註意,不要聯結不必要的表。聯結的表越多,性能下降越厲害。
註意:聯結中表的最大數目
雖然 SQL 本身不限制每個聯結約束中表的數目,但實際上許多 DBMS 都有限制。請參閱具體的 DBMS 文檔以瞭解其限制。
現在回顧一下 如何在 SQL 中使用子查詢 中的例子,如下的 SELECT
語句返回訂購產品 RGAN01
的顧客列表:
SELECT cust_name, cust_contact
FROM Customers
WHERE cust_id IN (SELECT cust_id
FROM Orders
WHERE order_num IN (SELECT order_num
FROM OrderItems
WHERE prod_id = 'RGAN01'));
如 如何在 SQL 中使用子查詢 所述,子查詢並不總是執行複雜 SELECT
操作的最有效方法,下麵是使用聯結的相同查詢:
SELECT cust_name, cust_contact
FROM Customers, Orders, OrderItems
WHERE Customers.cust_id = Orders.cust_id
AND OrderItems.order_num = Orders.order_num
AND prod_id = 'RGAN01';
輸出:
cust_name cust_contact
----------------------------- --------------------
Fun4All Denise L. Stephens
The Toy Store Kim Howard
如 如何在 SQL 中使用子查詢 所述,這個查詢中的返回數據需要使用 3 個表。
但在這裡,我們沒有在嵌套子查詢中使用它們,而是使用了兩個聯結來連接表。這裡有三個 WHERE
子句條件。前兩個關聯聯結中的表,後一個過濾產品 RGAN01
的數據。
提示:多做實驗
可以看到,執行任一給定的 SQL 操作一般不止一種方法。很少有絕對正確或絕對錯誤的方法。
性能可能會受操作類型、所使用的 DBMS、表中數據量、是否存在索引或鍵等條件的影響。
因此,有必要試驗不同的選擇機制,找出最適合具體情況的方法。
說明:聯結的列名
上述所有例子里,聯結的幾個列的名字都是一樣的(例如
Customers
和Orders
表裡的列都叫cust_id
)。列名相同並不是必需的,而且你經常會遇到命名規範不同的資料庫。我這樣建表只是為了簡單起見。
三、小結
聯結是 SQL 中一個最重要、最強大的特性,有效地使用聯結需要對關係資料庫設計有基本的瞭解。
本文在介紹聯結時,講述了一些關係資料庫設計的基本知識,包括等值聯結(也稱為內聯結)這種最常用的聯結。
SQL 如何使用自聯結、自然聯結和外聯結 介紹如何創建其他類型的聯結。
原文鏈接:https://www.developerastrid.com/sql/sql-inner-join/
(完)