第1課 瞭解 SQL
SQL(Structured Query Language)是一種專門用來與資料庫溝通的語言。
資料庫(database):儲存有組織的資料的容器(通常是一個檔案或一組檔案)。
- 人們通常用資料庫這個術語來代表他們使用的資料庫軟體,這是不正確的,也因此產生了許多混淆。確切地說,資料庫軟體應稱為資料庫管理系統(DBMS)。資料庫是通過 DBMS 建立和操縱的容器,而具體它究竟是什麼,形式如何,各種資料庫都不一樣。
表(table):某種特定型別資料的結構化清單。
- 這裡的關鍵一點在於,儲存在表中的資料是同一種型別的資料或清單。
- 資料庫中的每個表都有一個名字來標識自己。
- 使表名成為唯一的,實際上是資料庫名和表名等的組合。有的資料庫還使用資料庫擁有者的名字作為唯一名的一部分。也就是說,雖然在相同資料庫中不能兩次使用相同的表名,但在不同的資料庫中完全可以使用相同的表名。
模式(schema):關於資料庫和表的佈局及特性的資訊。
列(column):關於資料庫和表的佈局及特性的資訊。
- 理解列的最好辦法是將資料庫表想象為一個網格,就像個電子表格那樣。網格中每一列儲存著某種特定的資訊。例如,在顧客表中,一列儲存顧客編號,另一列儲存顧客姓名,而地址、城市、州以及郵政編碼全都儲存在各自的列中。
- 資料庫中每個列都有相應的資料型別。資料型別(datatype)定義了列可以儲存哪些資料種類。
行(row):表中的一個記錄。
- 你可能聽到使用者在提到行時稱其為資料庫記錄(record)。這兩個術語多半是可以交替使用的,但從技術上說,行才是正確的術語。
主鍵(primary key):一列(或一組列),其值能夠唯一標識表中每一行。
- 任意兩行都不具有相同的主鍵值;
- 每一行都必須具有一個主鍵值(主鍵列不允許 NULL 值);
- 主鍵列中的值不允許修改或更新;
- 主鍵值不能重用(如果某行從表中刪除,它的主鍵不能賦給以後的新行)。
第2課 檢索資料
SELECT 語句
為了使用 SELECT 檢索表資料,必須至少給出兩條資訊——想選擇什麼,以及從什麼地方選擇。
結束 SQL 語句:多條 SQL 語句必須以分號(;)分隔。多數 DBMS 不需要在單條 SQL 語句後加分號,但也有 DBMS 可能必須在單條 SQL 語句後加上分號。當然,如果願意可以總是加上分號。事實上,即使不一定需要,加上分號也肯定沒有壞處。
SQL 語句和大小寫:請注意,SQL 語句不區分大小寫,因此 SELECT 與 select 是相同的。同樣,寫成 Select 也沒有關係。許多 SQL 開發人員喜歡對 SQL 關鍵字使用大寫,而對列名和表名使用小寫,這樣做使程式碼更易於閱讀和除錯。不過,一定要認識到雖然 SQL 是不區分大小寫的,但是表名、列名和值可能有所不同(這有賴於具體的 DBMS 及其如何配置)。
使用空格:在處理 SQL 語句時,其中所有空格都被忽略。
檢索單個列
SELECT prod_name
FROM Products;
複製程式碼
檢索多個列
SELECT prod_id, prode_name, prod_price
FROM Products;
複製程式碼
當心逗號:在選擇多個列時,一定要在列名之間加上逗號,但最後一個列名後不加。如果在最後一個列名後加了逗號,將出現錯誤。
檢索所有列
SELECT *
FROM Products;
複製程式碼
使用萬用字元:一般而言,除非你確實需要表中的每一列,否則最好別使用*萬用字元。雖然使用萬用字元能讓你自己省事,不用明確列出所需列,但檢索不需要的列通常會降低檢索和應用程式的效能。
檢索不同的值
SELECT DISTINCT vend_id
FROM Products;
複製程式碼
限制結果
# 檢索前 5 行
SELECT prod_name
FROM Products
LIMIT 5;
# 從第 3 行開始檢索 5 行
SELECT prod_name
FROM Products
LIMIT 5 OFFSET 3;
# or
SELECT prod_name
FROM Products
LIMIT 3, 5;
複製程式碼
第 0 行:第一個被檢索的行是第 0 行,而不是第 1 行。因此,LIMIT 1 OFFSET 1 會檢索第 2 行,而不是第 1 行。
註釋
- 單行註釋:
# 單行註釋
- 行內註釋:
SELECT prod_name FROM Products; -- 行內註釋
- 塊級註釋:
/* 塊級註釋 */
第3課 排序檢索資料
排序資料
SELECT prod_name
FROM Products
ORDER BY prode_name;
複製程式碼
ORDER BY 子句的位置:在指定一條 ORDER BY 子句時,應該保證它是 SELECT 語句中最後一條子句。如果它不是最後的子句,將會出現錯誤訊息。
按多個列排序
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price, prod_name;
複製程式碼
按列位置排序
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY 2, 3;
複製程式碼
指定排序方向
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price DESC;
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price DESC, prod_name;
複製程式碼
請注意,DESC 是 DESCENDING 的縮寫,這兩個關鍵字都可以使用。與 DESC 相對的是 ASC(或ASCENDING),在升序排序時可以指定它。但實際上,ASC 沒有多大用處,因為升序是預設的(如果既不指定 ASC 也不指定 DESC,則假定為 ASC)。
第4課 過濾資料
使用 WHERE 子句
在 SELECT 語句中,資料根據 WHERE 子句中指定的搜尋條件進行過濾。WHERE 子句在表名(FROM 子句)之後給出。
SELECT prod_name, prod_price
FROM Products
WHERE prod_price = 3.49;
SELECT prod_name, prod_price
FROM Products
WHERE prod_price < 10;
複製程式碼
SQL 過濾與應用過濾:資料也可以在應用層過濾。為此,SQL 的 SELECT 語句為客戶端應用檢索出超過實際所需的資料,然後客戶端程式碼對返回資料進行迴圈,提取出需要的行。 通常,這種做法極其不妥。優化資料庫後可以更快速有效地對資料進行過濾。而讓客戶端應用(或開發語言)處理資料庫的工作將會極大地影響應用的效能,並且使所建立的應用完全不具備可伸縮性。此外,如果在客戶端過濾資料,伺服器不得不通過網路傳送多餘的資料,這將導致網路頻寬的浪費。
WHERE 子句的位置:在同時使用 ORDER BY 和 WHERE 子句時,應該讓 ORDER BY 位於 WHERE 之後,否則將會產生錯誤。
WHERE 子句操作符
=
:等於>
:大於>=
:大於等於<>
:不等於!=
:不等於!>
:不大於!<
:不小於<
:小於<=
:小於等於BETWEEN
:在指定的兩個值之間IS NULL
:為 NULL 值
操作符相容:某些操作符是冗餘的(如 <> 與 != 相同,!< 相當於 >=)。並非所有 DBMS 都支援這些操作符。
不匹配檢查
SELECT prod_name, prod_price
FROM Products
WHERE vend_id != 'DLL01’;
複製程式碼
何時使用引號:如果仔細觀察上述 WHERE 子句中的條件,會看到有的值括在單引號內,而有的值未括起來。單引號用來限定字串。如果將值與字串型別的列進行比較,就需要限定引號。用來與數值列進行比較的值不用引號。
範圍值檢查
SELECT prod_name, prod_price
FROM Products
WHERE prod_price BETWEEN 5 AND 10;
複製程式碼
空值檢查
SELECT prod_name, prod_price
FROM Products
WHERE prod_price IS NULL;
複製程式碼
第5課 高階資料過濾
為了進行更強的過濾控制,SQL 允許給出多個 WHERE 子句。這些子句有兩種使用方式,即以 AND 子句或 OR 子句的方式使用。
組合 WHERE 子句
SELECT prod_id, prod_price, prod_name
FROM Products
WHERE vend_id = 'DLL01' AND prod_price <= 4;
SELECT prod_id, prod_price, prod_name
FROM Products
WHERE vend_id = 'D
複製程式碼
AND: 用在 WHERE 子句中的關鍵字,用來指示檢索滿足所有給定條件的行。
OR:WHERE 子句中使用的關鍵字,用來表示檢索匹配任一給定條件的行。
求值順序
# 優先匹配 AND 左右兩側的條件
SELECT prod_id, prod_price, prod_name
FROM Products
WHERE vend_id = 'DLL01' OR vend_id = 'BRS01' AND prod_price >= 10;
# 優先匹配括號內的條件
SELECT prod_id, prod_price, prod_name
FROM Products
WHERE (vend_id = 'DLL01' OR vend_id = 'BRS01') AND prod_price >= 10;
複製程式碼
SQL 在處理 OR 操作符之前,優先處理 AND 操作符。圓括號具有更高的優先順序。
IN 操作符
IN 操作符用來指定條件範圍,範圍中的每個條件都可以進行匹配。
SELECT prod_name, prod_price
FROM Products
WHERE vend_id IN ( 'DLL01', 'BRS01' )
ORDER BY prod_name;
複製程式碼
IN:WHERE 子句中用來指定要匹配值的清單的關鍵字,功能與 OR 相當。
NOT 操作符
WHERE 子句中 NOT 操作符有且只有一個功能,那就是否定其後所跟的任何條件。
SELECT prod_name
FROM Products
WHERE NOT vend_id = 'DLL01'
ORDER BY prod_name;
複製程式碼
NOT:WHERE 子句中用來否定其後條件的關鍵字。
第6課 用萬用字元進行過濾
LIKE 操作符
為在搜尋子句中使用萬用字元,必須使用 LIKE 操作符。LIKE 指示 DBMS,後跟的搜尋模式利用萬用字元匹配而不是簡單的相等匹配進行比較。
萬用字元(wildcard):用來匹配值的一部分的特殊字元。
搜尋模式(search pattern):由字面值、萬用字元或兩者組合構成的搜尋條件。
百分號(%)萬用字元
在搜尋串中,% 表示任何字元出現任意次數(包括 0 個字元)。
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE 'Fish%';
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE '%bean bag%';
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE 'F%y';
複製程式碼
下劃線(_)萬用字元
下劃線的用途與 % 一樣,但它只匹配單個字元,而不是多個字元。
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE '__ inch teddy bear';
複製程式碼
方括號([])萬用字元
方括號([])萬用字元用來指定一個字符集,它必須匹配指定位置(萬用字元的位置)的一個字元。
SELECT cust_contact
FROM Customers
WHERE cust_contact LIKE '[JM]%' ORDER BY cust_contact;
SELECT cust_contact
FROM Customers
WHERE cust_contact LIKE '[^JM]%' ORDER BY cust_contact;
SELECT cust_contact
FROM Customers
WHERE NOT cust_contact LIKE '[JM]%' ORDER BY cust_contact;
複製程式碼
SQL 的萬用字元很有用。但這種功能是有代價的,即萬用字元搜尋一般比前面討論的其他搜尋要耗費更長的處理時間。
第7課 建立計算欄位
計算欄位
儲存在資料庫表中的資料一般不是應用程式所需要的格式,下面舉幾個例子。
- 需要顯示公司名,同時還需要顯示公司的地址,但這兩個資訊儲存在不同的表列中。
- 城市、州和郵政編碼儲存在不同的列中(應該這樣),但郵件標籤列印程式需要把它們作為一個有恰當格式的欄位檢索出來。
- 列資料是大小寫混合的,但報表程式需要把所有資料按大寫表示出來。
- 物品訂單表儲存物品的價格和數量,不儲存每個物品的總價格(用價格乘以數量即可)。但為列印發票,需要物品的總價格。
- 需要根據表資料進行諸如總數、平均數的計算。
我們需要直接從資料庫中檢索出轉換、計算或格式化過的資料,而不是檢索出資料,然後再在客戶端應用程式中重新格式化。
這就是計算欄位可以派上用場的地方了。與前幾課介紹的列不同,計算欄位並不實際存在於資料庫表中。計算欄位是執行時在 SELECT 語句內建立的。
拼接欄位
SELECT vend_name + ' (' + vend_country + ')'
FROM Vendors
ORDER BY vend_name;
# or
SELECT vend_name || ' (' || vend_country || ')'
FROM Vendors
ORDER BY vend_name;
複製程式碼
許多資料庫(不是所有)儲存填充為列寬的文字值,而實際上你要的結果不需要這些空格。為正確返回格式化的資料,必須去掉這些空格。這可以使用 SQL 的 RTRIM() 函式來完成。
SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')'
FROM Vendors
ORDER BY vend_name;
複製程式碼
RTRIM()
:去掉字串右邊的空格。LTRIM()
:去掉字串左邊的空格。TRIM()
:去掉字串兩邊的空格。
使用別名
SELECT vend_name + ' (' + vend_country + ')' AS vend_title
FROM Vendors
ORDER BY vend_name;
複製程式碼
執行算術計算
SELECT prod_id, quantity, item_price, quantity*item_price AS expanded_price
FROM OrderItems
WHERE order_num = 20008;
複製程式碼
SQL 支援的算術操作符:
+
:加-
:減*
:乘/
:除
第8課 使用函式處理資料
函式帶來的問題
事實上,只有少數幾個函式被所有主要的 DBMS 等同地支援。雖然所有型別的函式一般都可以在每個 DBMS 中使用,但各個函式的名稱和語法可能極其不同。
大多數 SQL 實現支援以下型別的函式:
- 用於處理文字字串(如刪除或填充值,轉換值為大寫或小寫)的文 本函式。
- 用於在數值資料上進行算術操作(如返回絕對值,進行代數運算)的數值函式。
- 用於處理日期和時間值並從這些值中提取特定成分(如返回兩個日期之差,檢查日期有效性)的日期和時間函式。
- 返回 DBMS 正使用的特殊資訊(如返回使用者登入資訊)的系統函式。
文字處理函式
SELECT vend_name, UPPER(vend_name) AS upper_vend_name
FROM Vendors
ORDER BY vend_name;
SELECT vend_name LENGTH(vend_name) AS vend_name_length
FROM Vendors
ORDER BY vend_name;
複製程式碼
常用的文字處理函式:
LEFT()
:返回字串左邊的字元LENGTH()
:返回字串的長度LOWER()
:將字串轉換為小寫LTRIM()
:去掉字串左邊的空格RIGHT()
:返回字串右邊的字元RTRIM()
:去掉字串右邊的空格SOUNDEX()
:返回字串的 SOUNDEX 值UPPER
:將字串轉換為大寫
SOUNDEX 是一個將任何文 本串轉換為描述其語音表示的字母數字模式的演算法。SOUNDEX 考慮了類似的發音字元和音節,使得能對字串進行發音比較而不是字母比較。
日期和時間處理函式
日期和時間採用相應的資料型別儲存在表中,每種 DBMS 都有自己的特殊形式。日期和時間值以特殊的格式儲存,以便能快速和有效地排序或過濾,並且節省物理儲存空間。
應用程式一般不使用日期和時間的儲存格式,因此日期和時間函式總是用來讀取、統計和處理這些值。由於這個原因,日期和時間函式在 SQL 中具有重要的作用。遺憾的是,它們很不一致,可移植性最差。
SELECT order_num
FROM Orders
WHERE strftime('%Y', order_date) = '2012';
複製程式碼
數值處理函式
常用數值處理函式:
ABS()
:返回一個數的絕對值COS()
:返回一個角度的餘弦EXP()
:返回一個數的指數值PI()
:返回圓周率SIN()
:返回一個角度的正弦SQRT()
:返回一個數的平方根TAN()
:返回一個角度的正切
第9課 彙總資料
聚集函式(aggregate function):對某些行執行的函式,計算並返回一個值。
SQL 聚集函式:
AVG()
:返回某列的平均值COUNT()
:返回某列的行數MAX()
:返回某列的最大值MIN()
:返回某列的最小值SUM()
:返回某列值之和
AVG() 函式
AVG() 通過對錶中行數計數並計算其列值之和,求得該列的平均值。AVG() 可用來返回所有列的平均值,也可以用來返回特定列或行的平均值。
SELECT AVG(prod_price) AS avg_price
FROM Products;
SELECT AVG(prod_price) AS avg_price
FROM Products
WHERE vend_id = 'DLL01';
複製程式碼
COUNT() 函式
COUNT() 函式進行計數。可利用 COUNT() 確定表中行的數目或符合特定條件的行的數目。
COUNT() 函式有兩種使用方式:
- 使用 COUNT(*) 對錶中行的數目進行計數,不管表列中包含的是空值(NULL)還是非空值。
- 使用 COUNT(column) 對特定列中具有值的行進行計數,忽略 NULL 值。
SELECT COUNT(*) AS num_cust
FROM Products;
SELECT COUNT(cust_email) AS num_cust
FROM Customers;
複製程式碼
MAX() 函式
MAX() 返回指定列中的最大值。
SELECT MAX(prod_price) AS max_price
FROM Products;
複製程式碼
對非數值資料使用 MAX():雖然 MAX() 一般用來找出最大的數值或日期值,但許多(並非所有)DBMS 允許將它用來返回任意列中的最大值,包括返回文字列中的最大值。在用於文字資料時,MAX() 返回按該列排序後的最後一行。
MIN() 函式
MIN() 的功能正好與 MAX() 功能相反,它返回指定列的最小值。
SELECT MIN(prod_price) AS min_price
FROM Products;
複製程式碼
對非數值資料使用 MIN():雖然 MIN() 一般用來找出最小的數值或日期值,但許多(並非所有)DBMS 允許將它用來返回任意列中的最小值,包括返回文字列中的最小值。在用於文字資料時,MIN() 返回該列排序後最前面的行。
SUM() 函式
SUM() 用來返回指定列值的和(總計)。
SELECT SUM(quantity) AS items_ordered
FROM OrderItems
WHERE order_num = 20005;
SELECT SUM(item_price * quantity) AS total_price
FROM OrderItems
WHERE order_num = 20005;
複製程式碼
聚集不同值
SELECT AVG(DISTINCT prod_price) AS avg_price
FROM Products
WHERE vend_id = 'DLL01;
複製程式碼
組合聚集函式
SELECT COUNT(*) AS num_items, MIN(prod_price) AS price_min, MAX(prod_price) AS price_max, AVG(prod_price) AS price_avg
FROM Products;
複製程式碼
第10課 分組資料
建立分組
SELECT vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY vend_id;
複製程式碼
在使用 GROUP BY 子句前,需要知道一些重要的規定。
- GROUP BY 子句可以包含任意數目的列,因而可以對分組進行巢狀,更細緻地進行資料分組。
- 如果在 GROUP BY 子句中巢狀了分組,資料將在最後指定的分組上進 行彙總。換句話說,在建立分組時,指定的所有列都一起計算(所以 不能從個別的列取回資料)。
- GROUP BY 子句中列出的每一列都必須是檢索列或有效的表示式(但 不能是聚集函式)。如果在 SELECT 中使用表示式,則必須在 GROUP BY 子句中指定相同的表示式。不能使用別名。
- 大多數 SQL 實現不允許 GROUP BY 列帶有長度可變的資料型別(如文字或備註型欄位)。
- 除聚集計算語句外, SELECT 語句中的每一列都必須在 GROUP BY 子句 中給出。
- 如果分組列中包含具有 NULL 值的行,則 NULL 將作為一個分組返回。 如果列中有多行 NULL 值,它們將分為一組。
- GROUP BY 子句必須出現在 WHERE 子句之後, ORDER BY 子句之前。
SELECT vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY vend_id HAVING COUNT(*) >= 2;
SELECT vend_Id, COUNT(*) AS num_prods
FROM Products
WHERE prod_price >= 4
GROUP BY vend_id HAVING COUNT(*) >= 2;
SELECT order_num, COUNT(*) AS items
FROM OrderItems
GROUP BY order_num HAVING COUNT(*) >= 3
ORDER BY items, order_num;
複製程式碼
SELECT 子句順序
子句 | 說明 | 是否必須使用 |
---|---|---|
SELECT | 要返回的列或表示式 | 是 |
FROM | 從中檢索資料的表 | 僅在從表選擇資料時使用 |
WHERE | 行級過濾 | 否 |
GROUP BY | 分組說明 | 僅在按組計算聚集時使用 |
HAVING | 組級過濾 | 否 |
ORDER BY | 輸出排序順序 | 否 |
第11課 使用子查詢
利用子查詢進行過濾
SELECT cust_id
FROM Orders
WHERE order_num IN (
SELECT order_num
FROM OrderItems
WHERE prod_id = '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'));
複製程式碼
只能是單列:作為子查詢的 SELECT 語句只能查詢單個列。企圖檢索多個列將返回錯誤。
子查詢和效能:這裡給出的程式碼有效,並且獲得了所需的結果。但是,使用子查詢並不總是執行這類資料檢索的最有效方法。
作為計算欄位使用子查詢
SELECT cust_name, cust_state, (
SELECT COUNT(*)
FROM Orders
WHERE Orders.cust_id = Customers.cust_id) AS orders
FROM Customers
ORDER BY cust_name;
複製程式碼
第12課 聯結表
關係表
相同的資料出現多次決不是一件好事,這是關聯式資料庫設計的基礎。關係表的設計就是要把資訊分解成多個表,一類資料一個表。各表通過某些共同的值互相關聯(所以才叫關聯式資料庫)。
關係資料可以有效地儲存,方便地處理。因此,關聯式資料庫的可伸縮性遠比非關聯式資料庫要好。
為什麼使用聯結
將資料分解為多個表能更有效地儲存,更方便地處理,並且可伸縮性更好。
簡單說,聯結是一種機制,用來在一條 SELECT 語句中關聯表,因此稱為聯結。使用特殊的語法,可以聯結多個表返回一組輸出,聯結在執行時關聯表中正確的行。
建立聯結
SELECT vend_name, prod_name, prod_price
FROM Vendors, Products
WHERE Vendors.vend_id = Products.vend_id;
複製程式碼
完全限定列名:就像前一課提到的,在引用的列可能出現歧義時,必須使用完全限定列名(用一個句點分隔表名和列名)。如果引用一個沒有用表名限制的具有歧義的列名,大多數 DBMS 會返回錯誤。
WHERE 子句的重要性
WHERE 子句作為過濾條件,只包含那些匹配給定條件(這裡是聯結條件)的行。沒有 WHERE 子句,第一個表中的每一行將與第二個表中的每一行配對,而不管它們邏輯上是否能配在一起。
笛卡爾積:由沒有聯結條件的表關係返回的結果為笛卡兒積。檢索出的行的數目將是第一個表中的行數乘以第二個表中的行數。
# 錯誤示範
SELECT vend_name, prod_name, prod_price
FROM Vendors, Products;
複製程式碼
內聯結
目前為止使用的聯結稱為等值聯結(equijoin),它基於兩個表之間的相等測試。這種聯結也稱為內聯結(inner join)。
SELECT vend_name, prod_name, prod_price
FROM Vendors INNER JOIN Products
ON Vendors.vend_id = Products.vend_id;
複製程式碼
在使用這種語法時,聯結條件用特定的 ON 子句而不是 WHERE 子句給出。傳遞給 ON 的實際條件與傳遞給 WHERE 的相同。
聯結多個表
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;
複製程式碼
效能考慮:DBMS 在執行時關聯指定的每個表,以處理聯結。這種處理可能非常耗費資源,因此應該注意,不要聯結不必要的表。聯結的表越多,效能下降越厲害。
多做實驗:執行任一給定的 SQL 操作一般不止一種方法。很少有絕對正確或絕對錯誤的方法。效能可能會受操作型別、所使用的 DBMS、表中資料量、是否存在索引或鍵等條件的影響。因此,有必要試驗不同的選擇機制,找出最適合具體情況的方法。
第13課 建立高階聯結
使用別名表
給列起別名的語法如下:
SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')' AS vend_title
FROM Vendors
ORDER BY vend_name;
複製程式碼
給表起別名的語法:
SELECT cust_name, cust_contact
FROM Customers AS C, Orders AS O, OrderItems AS OI
WHERE C.cust_id = O.cust_id
AND OI.order_num = O.order_num
AND prod_id = 'RGAN01';
複製程式碼
需要注意,表別名只在查詢執行中使用。與列別名不一樣,表別名不返回到客戶端。
使用不同型別的聯結
迄今為止,我們使用的只是內聯結或等值聯結的簡單聯結。現在來看三種其他聯結:自聯結(self-join)、自然聯結(natural join)和外聯結(outer join)。
自聯結
SELECT c1.cust_id, c1.cust_name, c1.cust_contact
FROM Customers AS c1, Customers AS c2
WHERE c1.cust_name = c2.cust_name
AND c2.cust_contact = 'Jim Jones';
複製程式碼
此查詢中需要的兩個表實際上是相同的表,因此 Customers 表在 FROM 子句中出現了兩次。雖然這是完全合法的,但對 Customers 的引用具有歧義性,因為 DBMS 不知道你引用的是哪個 Customers 表。解決此問題,需要使用表別名。
用自聯結而不用子查詢:自聯結通常作為外部語句,用來替代從相同表中檢索資料的使用子查詢語句。雖然最終的結果是相同的,但許多 DBMS 處理聯結遠比處理子查詢快得多。應該試一下兩種方法,以確定哪一種的效能更好。
自然聯結
無論何時對錶進行聯結,應該至少有一列不止出現在一個表中(被聯結的列)。標準的聯結(前一課中介紹的內聯結)返回所有資料,相同的列甚至多次出現。自然聯結排除多次出現,使每一列只返回一次。
自然聯結要求你只能選擇那些唯一的列,一般通過對一個表使用萬用字元(SELECT *),而對其他表的列使用明確的子集來完成。
SELECT C.*, O.order_num, O.order_date, OI.prod_id, OI.quantity, OI.item_price
FROM Customers AS C, Orders AS O, OrderItems AS OI
WHERE C.cust_id = O.cust_id
AND OI.order_num = O.order_num
AND prod_id = 'RGAN01';
複製程式碼
外聯結
許多聯結將一個表中的行與另一個表中的行相關聯,但有時候需要包含沒有關聯行的那些行。
SELECT Customers.cust_id, Orders.order_num
FROM Customers LEFT OUTER JOIN orders
ON Customers.cust_id = Orders.cust_id;
複製程式碼
與內聯結關聯兩個表中的行不同的是,外聯結還包括沒有關聯行的行。在使用 OUTER JOIN 語法時,必須使用 RIGHT 或 LEFT 關鍵字指定包括其所有行的表(RIGHT指出的是 OUTER JOIN 右邊的表,而LEFT指出的是 OUTER JOIN左邊的表)。上面的例子使用 LEFT OUTER JOIN 從 FROM 子句左邊的表(Customers 表)中選擇所有行。為了從右邊的表中選擇所有行,需要使用 RIGHT OUTER JOIN。
SELECT Customers.cust_id, Orders.order_num
FROM Customers RIGHT OUTER JOIN Orders
ON Orders.cust_id = Customers.cust_Id;
複製程式碼
外聯結的型別:有兩種基本的外聯結形式:左外聯結和右外聯結。它們之間的唯一差別是所關聯的表的順序。換句話說,調整 FROM 或 WHERE 子句中表的順序,左外聯結可以轉換為右外聯結。因此,這兩種外聯結可以互換使用,哪個方便就用哪個。
還存在另一種外聯結,就是全外聯結(full outer join),它檢索兩個表中的所有行並關聯那些可以關聯的行。與左外聯結或右外聯結包含一個表的不關聯的行不同,全外聯結包含兩個表的不關聯的行。
SELECT Customers.cust_id, Orders.order_num
FROM Orders FULL OUTER JOIN Customers
ON Orders.cust_id = Customers.cust_id;
複製程式碼
使用帶聚集函式的聯結
SELECT Customers.cust_id, COUNT(Orders.order_num) AS num_ord
FROM Customers INNER JOIN Orders
ON Customers.cust_id = Orders.cust_id
GROUP BY Customers.cust_id;
複製程式碼
聚集函式也可以方便地與其他聯結一起使用。
SELECT Customers.cust_id, COUNT(Orders.order_num) AS num_ord
FROM Customers LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id
GROUP BY Customers.cust_id;
複製程式碼