1. 程式人生 > >SQL SERVER 2008 程式設計入門經典讀書筆記 -- SQL Server 複雜查詢

SQL SERVER 2008 程式設計入門經典讀書筆記 -- SQL Server 複雜查詢

1. 子查詢的概念

子查詢是巢狀在另一個查詢中的普通 T-SQL 查詢,在有一個 SELECT 語句作為另一個費查詢的部分資料或條件的基礎時,通過括號建立子查詢。

子查詢通常用於滿足下列某個需求。

  • 將一個查詢分解為一系列的邏輯步驟
  • 提供一個列表作為 WHERE 子句和 [IN | EXISTS | ANY | ALL ] 的目標
  • 為父查詢中的每個記錄提供一個查詢表
值得注意的是,大部分子查詢可以使用連線來編寫。在可以使用連線的地方,由於很多原因連線是首選的方式。

1.1 構建巢狀子查詢

巢狀子查詢只在一個方向巢狀 -- 返回在。如果希望返回一個列表,外部查詢中使用的單個值,或者與 IN 運算子一起使用的一個完整的值列表。在需要使用一個顯示的的 “=” 運算子時,將使用一個返回單個值的查詢 -- 這就是一行中的一列。那麼需要再外部查詢中使用 IN 運算子。 從不嚴格意思上說,查詢語法看起來像下面的兩個語法模板:
        SELECT <SELECT list>
	FROM <SomeTable>
	WHERE <SomeColumn> = (
			SELECT <single column>
			FROM <SomeTable>
			WHERE <condition that results in only one row returned>
		)
或者:
SELECT <SELECT list>
	FROM <SomeTable>
	WHERE <SomeColumn> IN (
			SELECT <single column>
			FROM <SomeTable>
			WHERE <condition>
		)
顯然,確切的語法會有所變化,不僅因為需要替換選擇列表和確切的表名稱,而且因為在內部查詢和/或外部查詢中有一個多表連線。

1.1.1 使用返回多個值的 SELECT 語句的巢狀查詢

下面通過一個顯式的示例來了解問題的本質。例如,假設希望知道第一天通過系統銷售的產品的每個條目的 ProductID (注意 OrderDate 為 Date 型別)
SELECT DISTINCT soh.OrderDate, sod.ProductID
	FROM Sales.SalesOrderHeader sod
	JOIN Sales.SalesOrderDetail sod on soh.SalesOrderID = sod.SalesOrderID
	WHERE OrderDate = (SELECT MIN(OrderDate) FROM Sales.SalesOrderHeader)	

1.1.2 使用返回多個值的子查詢的巢狀查詢

也許,最常見的一種子查詢是檢索某些形式的域列表,然後用作另一個查詢的條件。 下面通過 查詢具有折扣資訊的所有產品的列表 的例子來進行了解。
SELECT ProductID, Name
	FROM Production.Product
	WHERE ProductID IN (
		SELECT ProductID FROM Sales.SpecialOfferProduct ):
	)	
儘管這種工作方式很好,但更好的方式是使用內部連線實現,而不是使用巢狀的 SELECT。例如,可以通過允許簡單的連線來得到同於的結果。
SELECT DISTINCT pp.ProductID, Name
	FROM Production.Product PP
	JOIN Sales.SpecialOfferProduction ssop on pp.ProductID = ssop.ProductID;
由於效能方面的考慮,如果沒有特別的理由使用巢狀的 SELECT 的話,則還應該使用連線方法作為預設解決方案。 提示: SQL SERVER 可以靈活處理這類問題。在大部分情況下,SQL SERVER 實際上會將巢狀子查詢方法解析為 和使用連線一樣的查詢計劃--事實上,如果檢查巢狀子查詢和前面的連線查詢計劃,就會發現他們是完全相同 的計劃。因此,在大多數情況下,這兩種方法是沒多大區別的。當然,這裡所說的是“大多數情況”。當查詢計劃 不同時,連線通常是更好的選擇,因此推薦在預設情況下使用連線的語法

1.1.3 使用巢狀的 SELECT 發現孤立的記錄

這種巢狀的 SELECT 和前面的示例幾乎相同,處理新增 NOT 運算子。這是得在轉換連線語法時設定為等同於 外部連線而不是內部連線。 下面看一下所有沒有匹配商品的折扣記錄:
SELECT Description
	FROM Sales.SpecialOffer sso
	WHERE sso.SpecialOfferID != 1
		AND sso.SpecialOfferID NOT IN 
		(SELECT SpecialOfferID FROM Sales.SpecialOfferProduct);
上面的功能也可以用外連線實現
SELECT Description
FROM Sales.SpecialOfferProduct ssop
RIGHT OUTER JOIN Sales.SpecialOffer sso on sso.SpecialOfferID = ssop.SpecialOfferID
WHERE sso.SpecialOfferID != 1 AND sso.SpecialOfferID IS NULL;

2 關聯子查詢

2.1 關聯子查詢的工作原理

關聯子查詢和上面的巢狀子查詢的不同之處在於資訊傳遞是雙向的,而不是單向的。在巢狀在查詢中,內部查詢只處理了一次,然後將資訊傳遞到外部查詢。 而外部查詢也只執行一次--提供和您自己可能輸入的完全相同的值或列表。 而在關聯子查詢中,內部查詢用2外部查詢提供的資訊允許,反之亦然。這似乎令人有些困惑,但是這是一個分3個步驟進行的·處理過程。 (1)外部查詢獲得一個記錄,然後將該記錄傳遞到內部查詢 (2)內部查詢根據傳遞的值執行 (3)然後內部查詢將結果值傳回外部查詢,而外部查詢利用這些值完成處理過程

2.2 在 WHERE 子句中的關聯子查詢

對於上面查詢系統中第一天下單的客戶資訊,考慮查詢系統中每個客戶第一天下單的日期。這裡就需要 一點技巧。當使用巢狀子查詢時,只在整個系統中查詢第一天--現在需要查詢每個客戶第一天下訂單 的日期。如果使用兩個單獨的查詢來完成,可以建立一個臨時表,然後與之進行連線。 使用臨時表的解決方案有點類似如下所示:
USE AdventureWorks2008;

-- Get a list of customers and the date of their first order
SELECT soh.CustomerID, MIN(soh.OrderDate)AS OrderDate
INTO #MinOrderSates
>FROM Sales.SalesOrderHeader soh
GROUP BY soh.CustomerID

-- Do something additional with that information
SELECT soh.CustomerID, soh.SalesOrderID, soh.OrderDate
FROM Sales.SalesOrderHeader soh
JOIN #MinOrderDates t on soh.CustomerID = t.CustomerID  and soh.OrderDate = t.OrderDate 
ORDER BY soh.CustomerID  

DROP TABLE #MinOrderDates;
有時,在不使用遊標的情況下,使用多查詢的方法是唯一的方法--不過這裡並不是這樣的。 如果希望在一個查詢中國允許,那麼需要找到一種方法來檢視每個使用者。可以讓內部查詢基於當前外部查詢的 CustomerID 來查詢。然後需要 將返回值傳遞給外部查詢。這樣可基於最早的訂單日期進行匹配。 程式碼如下所示:
SELECT sob1.CustomerID, sob1.SalesOrderID, sob1.OrderDate
FROM Sales.SalesOrderDetail soh1
WHERE soh1.OrderDate = (
	SELECT Min(soh2.OrderDate) FROM Sales.SalesOrderDetail soh2
	WHERE soh2.CustomerID = soh1.CustomerID 
	)
ORDER BY CustomerID;
在這個特殊查詢中,外部查詢只在 WHERE 子句中引用內部查詢--也可以在選擇列表中包含來自內部查詢的資料。

2.3 在 SELECT 列表中的關聯子查詢

子查詢也可以用於在選擇結果中提供不同的答案。當所要查詢的資訊與查詢中飯的其他資訊完全不同時,經常出現這種情況(例如,需要一個欄位的 聚合結果,但是又不希望所有這些資料影響其他返回的欄位)。 這裡查詢客戶的賬號和在哪天開始訂購某物。程式碼如下所示:
SELECT sc.AccountNumber,
	(SELECT Min(OrderDate) FROM Sales.SalesOrderHeader soh
	  WHERE sob.CustomerID = sc.CustomerID) AS OrderDate
FROM Sales.Customer sc;

3. MERGE 命令

MERGE 命令是SQL SERVER 2008中的新功能,它提供了一種不同的方式來使用 DML語句。通過 MERGE命令,可以將多個 DML動作語句(INSERT、UPDATE、DELETE) 組合成一個整體動作,從而提高效能(他們可共享許多相同的物理操作)和簡化事物。MERGE 利用一個特殊的 USING 子句,該子句有點類似於 CTE. 然後,USING 子句中的結果可按條件應用於 INSERT、UPDATE 和 DELETE 語句,其基本語法如下所示: 下面採用一個收貨庫存的示例來說明。假定為了做報表,建立了一個特殊的銷售彙總表。希望每日執行查詢,將新的銷售記錄新增到每月彙總表中。 在某月的第一個晚上,幾乎沒什麼工作可做,因為該月沒有其他彙總記錄,該天的銷售記錄也已彙總並插入了。但在第二天晚上,情況不同了,需要 像第一天那樣彙總並插入新紀錄,但(對於該月銷售的產品)只需要更新已有記錄。 下面看一下 MERGE 命令如何在一個步驟中同時管理兩個動作。在這之前,需要建立彙總表:

		USE AdventureWorks2008
		CREATE TABLE Sales.MonthlyRollup
		(
		  Year smallint NOT NULL,
			 Month tinyint NOT NULL,
			 ProductID int not null FOREIGN KEY REFERENCES Production.Product(ProductID),
			 QtySold int NOT NULL,
			 CONSTRAINT PKYearMonthProductID PRIMARY KEY(Year, Monh, ProductID)
		 );
編寫 MERGE 語句如下
MERGE Sales.MonthlyRollup AS smr
		USING
		(
		 SELECT soh.OrderDate, sod.ProductID, SUM(sod.OrderQty) AS QtySold
		 FROM Sales.SalesOrderHeader soh
		 JOIN Sales.SalesOrderDetail  sod on soh.SalesOrderID = sod.SalesOrderID
		 WHERE soh.OrderDate >= '2003-08-22' AND soh.OrderDate < '2003-08-23'
		 GROUP BY soh.OrderDate, sod.ProductID
		) AS s
		ON (s.ProductID = smr.ProductID)
		WHEN MATCHED THEN
			UPDATE SET smt.OtySold = smr.QtySold + s.QtySold 
		WHEN NOT MATCHED THEN
			INSERT (Year, Month, ProductID, QtySold)
			VALUES (DATEPART(yy, s.OrderDate),
					DATEPART(m, s.OrderDate),
					s.ProductID,
					s.QtySold);