使用自足式子查詢或相互關聯的子查詢
先前,我們看過自足式子查詢,其中,內部查詢與外部查詢無關,只執行一次,然後將結果傳回給外部查詢。 T-SQL 也支援「相互關聯」的子查詢,其中,內部查詢會參考外部查詢中的資料行,在概念上針對每個資料列各執行一次。
使用相互關聯的子查詢
如同自足式子查詢,相互關聯的子查詢也是巢狀在外部查詢之內的 SELECT 陳述式。 相互關聯的子查詢也可以是純量子查詢或多重值子查詢。 通常是在內部查詢需要參考外部查詢中的值時使用。
但是,有別於自足式子查詢,使用相互關聯的子查詢時有一些特殊考慮:
- 相互關聯的子查詢與外部查詢不能分開執行。 此限制使測試和偵錯變得複雜。
- 不同於自足式子查詢只處理一次,相互關聯的子查詢會執行多次。 邏輯上,外部查詢會先執行,然後針對每個傳回的資料列,再處理內部查詢。
下列範例使用相互關聯的子查詢,傳回每個客戶的最新訂單。 子查詢會參考外部查詢,並在其 WHERE 子句中參考外部查詢的 CustomerID 值。 針對外部查詢中的每個資料列,子查詢會尋找該資料列中所參考之客戶的最大訂單識別碼,而外部查詢會檢查目前的資料列是否為具有該訂單識別碼的資料列。
SELECT SalesOrderID, CustomerID, OrderDate
FROM SalesLT.SalesOrderHeader AS o1
WHERE SalesOrderID =
(SELECT MAX(SalesOrderID)
FROM SalesLT.SalesOrderHeader AS o2
WHERE o2.CustomerID = o1.CustomerID)
ORDER BY CustomerID, OrderDate;
撰寫相互關聯的子查詢
若要撰寫相互關聯的子查詢,請考慮下列指導方針:
- 撰寫外部查詢接受來自內部查詢的適當傳回結果。 如果內部查詢是純量,您可以在 WHERE 子句中使用等式比較運算子,例如 =、<、> 和 <>。 如果內部查詢可能會傳回多個值,請使用 IN 述詞。 規劃處理 NULL 結果。
- 識別外部查詢中的資料行,此資料行將由相互關聯子的查詢參考。 在外部查詢中,為資料行的來源資料表宣告別名。
- 識別內部資料表中的資料行,此資料行將與外部資料表中的資料行比較。 建立來源資料表的別名,就像對外部查詢所做的一樣。
- 撰寫內部查詢,根據外部查詢的輸入值,從來源取得值。 例如,在內部查詢的 WHERE 子句中使用外部資料行。
當內部查詢參考外部值來作比較時,就形成內部查詢和外部查詢之間的相互關聯。 此相互關聯即為子查詢的名稱由來。
使用 EXISTS
T-SQL 除了從子查詢中取出值,還提供機制來檢查是否會從查詢傳回任何結果。 EXISTS 述詞判斷是否有任何資料列符合指定的條件,但不傳回資料列,而是傳回 TRUE 或 FALSE。 此技術很適合驗證資料,不會引起取出和處理結果的額外負荷。
使用 EXISTS 述詞將子查詢與外部查詢聯繫起來時,SQL Server 會以特殊方式處理子查詢的結果。 EXISTS 只檢查結果中是否有任何資料列,而不會從子查詢取出純量值或多重值清單。
概念上,EXISTS 述詞相當於取出結果、算出傳回的資料列計數,然後比較計數是否為零。 請比較下列查詢,其中針對已下訂單的客戶,將傳回其詳細資料:
第一個範例查詢在子查詢中使用 COUNT:
SELECT CustomerID, CompanyName, EmailAddress
FROM Sales.Customer AS c
WHERE
(SELECT COUNT(*)
FROM Sales.SalesOrderHeader AS o
WHERE o.CustomerID = c.CustomerID) > 0;
第二個查詢傳回相同的結果,但使用 EXISTS:
SELECT CustomerID, CompanyName, EmailAddress
FROM Sales.Customer AS c
WHERE EXISTS
(SELECT *
FROM Sales.SalesOrderHeader AS o
WHERE o.CustomerID = c.CustomerID);
在第一個範例中,子查詢必須將 Sales.SalesOrderHeader 資料表中找到的每個custid 算出所有出現次數,然後比較計數結果是否為零,簡單指出客戶已下訂單。
在第二個查詢中,一旦在 Sales.SalesOrderHeader 資料表中找到相關的訂單,EXIST 就立刻針對 custid 傳回 TRUE。 沒有必要將每一次出現全部計算在內。 另請注意,使用 EXISTS 形式時,子查詢不限於只傳回單一資料行。 請看以下的 SELECT *。 傳回的資料行無關緊要,因為我們只檢查究竟是否傳回任何資料列,而不檢查這些資料列中的值。
從邏輯處理的觀點來看,這兩種查詢形式相同。 從效能的觀點來看,資料庫引擎為了以最佳方式執行查詢,可能採用不同的處理方式。 請考慮依自己的使用方式來測試每一種查詢。
注意
如果您要將使用 COUNT(*) 的子查詢轉換成使用 EXISTS 的子查詢,請確保子查詢使用 SELECT *,而不是 SELECT COUNT(*)。 SELECT COUNT (*) 一律傳回一個資料列,因此 EXISTS 一律傳回 TRUE。
EXISTS 還有另一種用途很有用,就是加上 NOT 來否定子查詢,如下列範例所示,其中傳回從未下訂單的任何客戶:
SELECT CustomerID, CompanyName, EmailAddress
FROM SalesLT.Customer AS c
WHERE NOT EXISTS
(SELECT *
FROM SalesLT.SalesOrderHeader AS o
WHERE o.CustomerID = c.CustomerID);
針對已下訂單的客戶,SQL Server 不需要傳回相關訂單的資料。 如果在 Sales.SalesOrderHeader 資料表中找到 custid,NOT EXISTS 會評估為 FALSE,評估很快就完成。
若要撰寫查詢來使用 EXISTS 處理子查詢,請考慮下列指導方針:
- 關鍵字 EXISTS 緊接在 WHERE 後面。 除非也使用 NOT,否則前面沒有任何資料行名稱 (或其他運算式)。
- 在子查詢內,使用 SELECT *。 子查詢不傳回任何資料列,因此不需要指定任何資料行。