T-SQL進階:超越基礎 Level 2:編寫子查詢

By Gregory Larsen, 2016/01/01 (首次發佈於: 2014/01/29)sql

關於系列

本文屬於進階系列:T-SQL進階:超越基礎數據庫

跟隨Gregory Larsen的T-SQL DML進階系列,其涵蓋了更多的高級方面的T-SQL語言,如子查詢。函數

在您開始建立超出基本Transact-SQL語句的更復雜的SQL代碼時,您可能會發現須要使用其餘SELECT語句的結果來限制查詢。 當在父Transact-SQL語句中嵌入SELECT語句時,這些嵌入式SELECT語句被稱爲子查詢或相關子查詢。 在「超越基礎」樓梯的這個層次上,我將討論一個子查詢的不一樣方面,在未來的一個層面上,我將討論相關的子查詢。工具

什麼是子查詢?

子查詢只是一個SELECT語句,它包含在另外一個Transact-SQL語句中。能夠在任何可使用表達式的地方使用子查詢。許多子查詢返回單個列值,由於它們與比較運算符(=,!=,<,<=,>,> =)或表達式結合使用。當子查詢不用做表達式或使用比較運算符時,它能夠返回多個值。此外,子查詢甚至能夠在FROM子句或關鍵字EXISTS中使用時返回多個列和值。sqlserver

子查詢容易在Transact-SQL語句中發現,由於它將是括號中的SELECT語句。因爲子查詢包含在Transact-SQL語句中,所以子查詢一般稱爲內部查詢。而包含子查詢的Transact-SQL語句被稱爲外部查詢。子查詢的另外一個特色是能夠獨立於外部查詢運行,而且將無錯誤地運行,而且可能返回一組行或空行集。性能

子查詢的另外一種形式是相關子查詢。可是相關的子查詢不能獨立於外部的Transact SQL語句運行。相關子查詢使用外部查詢中的列或列來約束從相關子查詢返回的結果。這對於本文的相關子查詢足夠了。我將在將來的樓梯文章中探索相關的子查詢。測試

使用子查詢時還須要考慮如下幾點:優化

  • ntext,text和image數據類型不容許從子查詢返回
  • ORDER BY子句不能用於子查詢,除非使用TOP操做符
  • 使用子查詢的視圖沒法更新
  • COMPUTE和INTO子句不能在子查詢中使用

子查詢示例數據示例

爲了演示如何使用子查詢,我將須要一些測試數據。 而不是建立本身的測試數據,個人全部示例都將使用AdventureWorks2008R2數據庫。 若是您想跟隨並在環境中運行個人示例,那麼您能夠從這裏下載AdventureWorks2008R2數據庫:http://msftdbprodsamples.code...code

返回單個值的子查詢的示例

如上所述,在表達式中使用的子查詢或返回比較運算符一側的值須要返回單個值。 Transact-SQL語句中有許多不一樣的地方,須要一個子查詢來返回單個列值,例如在選擇列表中WHERE子句等。在本節中,我將提供一系列示例,演示如何使用子查詢 做爲表達式或與比較運算符以知足不一樣的業務需求。server

列列表中的子查詢

列列表中的子查詢是SELECT語句,它返回放置在SELECT子句的列列表中的單個列值。 爲了演示如何在選擇列表中使用子查詢,咱們假設咱們必須從具備如下業務需求的SELECT語句生成一個結果集:

  • 返回全部Sales.SalesOrderHeader記錄有什麼有OrderDate等於「2007-02-19 00:00:00.000」
  • 經過SalesOrderID命令返回的記錄
  • 編號每行返回的最舊的順序的RowNumber爲1,next oldest的RowNumber爲2等
  • 結果集須要一個名爲TotalOrders的列,須要使用等於「2007-02-19 00:00:00.000」的OrderDate的總訂單數量進行填充

清單1中列出了知足這些要求的代碼。

SELECT ROW_NUMBER() OVER (ORDER BY SalesOrderID) RowNumber
      , (SELECT COUNT(*) 
         FROM [Sales].[SalesOrderHeader] 
         WHERE ModifiedDate = '2007-02-19 00:00:00.000') 
                     AS TotalOrders
      , *
FROM [Sales].[SalesOrderHeader]
WHERE OrderDate = '2007-02-19 00:00:00.000';
清單1:列列表中的子查詢

在這個單一的Transact-SQL語句中,您會看到兩個不一樣的SELECT子句。 子查詢是嵌入在清單1中的語句中間的SELECT語句,它在它周圍有括號。 我已經刪除了子查詢語句,並將其放在清單2中,以防您想要測試以驗證它能夠獨立於完整的Transact-SQL語句運行。

SELECT COUNT(*) 
FROM [Sales].[SalesOrderHeader]
WHERE OrderDate = '2007-02-19 00:00:00.000'
清單2:清單1中的子查詢語句

經過將此子查詢列在列列表中,清單1中的此Transact-SQL語句能夠對OrderDate爲「2007-02-19 00:00:00.000」的SalesOrderHeader行的數量進行計數,並將該信息與詳細信息一塊兒返回 有關具備相同OrderDate值的Sales.SalesOrderHeader記錄的行信息。

WHERE子句中子查詢的示例

有時你想根據SELECT語句的結果來驅動WHERE子句條件。 當您在WHERE子句中的SELECT語句時,此SELECT語句其實是一個子查詢。 要演示在WHERE子句中使用子查詢,假設您須要顯示包含購買超大型長袖徽標運動衫的Sales.SalesOrderDetail記錄。 清單3中的代碼經過使用子查詢來知足個人顯示要求。

SELECT * FROM [Sales].[SalesOrderDetail]
WHERE ProductID = (SELECT ProductID 
                   FROM [Production].[Product]
                    WHERE Name = 'Long-Sleeve Logo Jersey, XL');
清單3:WHERE子句中的子查詢

清單3中的子查詢位於WHERE條件的右側。 此子查詢標識Product.Product記錄的ProductID,其中產品名稱爲「Long-Sleeve Logo Jersey,XL」。 此子查詢容許我找到具備與「Long-Sleeve Logo Jersey,XL」的產品名稱相關聯的ProductID的全部Sales.SalesOrderDetail記錄。

使用子查詢來控制TOP條款的示例

使用TOP子句返回的行數能夠由表達式控制。 清單5中的代碼標識了應該根據TOP子句中的子查詢返回的Sales.SalesOrderDetail行的數量。

SELECT TOP (SELECT TOP 1 OrderQty 
            FROM [Sales].[SalesOrderDetail]
            ORDER BY ModifiedDate) *  
FROM [Sales].[SalesOrderDetail]
WHERE ProductID = 716;
清單4:TOP子句中的子查詢

清單4中的代碼使用從子查詢返回的OrderQty值來標識將在TOP子句中使用的值。 經過使用子查詢來控制TOP子句返回的行數,能夠構建一個子查詢,以便在運行時動態地識別從查詢返回的行數。

子條款示例

爲了演示在HAVING子句中使用子查詢,假設您具備如下業務要求:

生成包含Sales.SalesOrderHeader.OrderDate和每一個日期的訂單數量的結果集,其中訂單數量超過「2006-05-01」上執行的訂單數量。

爲了知足這個要求,我開發了清單6中使用HAVING子句中的子查詢的查詢。

SELECT count(*), OrderDate 
FROM [Sales].[SalesOrderHeader]
GROUP BY OrderDate
HAVING count(*) >
       (SELECT count(*) 
        FROM [Sales].[SalesOrderHeader]
        WHERE OrderDate = '2006-05-01 00:00:00.000');
清單5:HAVING子句中的子查詢

清單5中的代碼具備HAVING子句右側的子查詢,並在個人子查詢中使用COUNT函數來肯定「2006-05-01」上的訂單數量。

在函數調用中使用子查詢的示例

要演示在函數調用中使用子查詢,假設您須要顯示OrderDate和每一個Sales.SalesOrderHeader記錄的最大OrderDate之間的天數。 清單6中的代碼符合此要求。

SELECT SalesOrderID
      , OrderDate
      ,DATEDIFF
          (
            dd,OrderDate
        ,(SELECT MAX(OrderDate)
          FROM [Sales].[SalesOrderHeader])
          ) AS DaysBetweenOrders
         ,(SELECT MAX(OrderDate)
        FROM [Sales].[SalesOrderHeader]) 
            AS MaxOrderDate
FROM [Sales].[SalesOrderHeader];
清單6:函數調用中的子查詢

清單6中的代碼有兩個不一樣的子查詢。 兩個子查詢返回Sales.SalesOrderHeader表中的最大OrderDate。 可是第一個子查詢用於將日期傳遞給DATEDIFF函數的第二個參數。

返回多個值的子查詢的示例

我迄今爲止的全部示例都包含僅在單個列中返回單個值的子查詢。 並非全部的子查詢都有這個要求。 接下來的幾個例子將使用返回多個值和/或多個列的子查詢。

FROM子句中的子查詢示例

在FROM子句中,一般會標識您的Transact-SQL語句將對其執行的表或表的集合。 每一個表提供一組記錄,您的查詢將用於肯定查詢的最終結果集。 子查詢能夠被認爲是返回一組記錄的查詢,所以它能夠像FROM表同樣在FROM子句中使用。 清單7中的查詢顯示了我如何在FROM子句中使用子查詢。 當在FROM子句中使用子查詢時,從子查詢生成的結果集一般稱爲派生表。

SELECT SalesOrderID 
FROM (SELECT TOP 10 SalesOrderID 
      FROM [Sales].[SalesOrderDetail]
      WHERE ProductID = 716
      ORDER BY ModifiedDate DESC) AS Last10SalesOrders;
清單7:FROM子句中的子查詢
  • 清單7中的代碼使用FROM子句中的子查詢來建立一個名爲Last10SalesOrders的表別名。 個人子查詢返回包含ProductID爲716的最後10個Sales.alesOrderDetail記錄。
  • 清單7中的代碼是一個很是簡單的例子,說明如何在FROM子句中使用子查詢。 經過在FROM子句中使用子查詢,您能夠輕鬆地構建更復雜的FROM語法,該語法將子查詢的結果與其餘表或其餘子查詢相結合,如清單8所示。

    SELECT DISTINCT OrderDate
    FROM (SELECT TOP 10 SalesOrderID 
          FROM [Sales].[SalesOrderDetail]
          WHERE ProductID = 716
          ORDER BY ModifiedDate DESC) AS Last10SalesOrders
    JOIN [Sales].[SalesOrderHeader] AS SalesOrderHeader
    ON Last10SalesOrders.SalesOrderID = SalesOrderHeader.SalesOrderID
    ORDER BY OrderDate
清單8:使用實際錶鏈接派生表

在清單8中,我看到了我在清單7中建立的子查詢/派生表,並將其與SalesOrderHeader表相加。 經過這樣作,我能夠肯定最後10次訂購ProductID = 716的OrderDate。

使用具備IN關鍵字的子查詢的示例

您能夠編寫一個返回列的多個值的子查詢的地方是當您的子查詢生成與IN關鍵字一塊兒使用的記錄集時。 清單9中的代碼演示瞭如何使用子查詢將值傳遞給IN關鍵字。

SELECT * FROM [Sales].[SalesOrderDetail] 
WHERE ProductID IN 
        (SELECT ProductID 
         FROM [Production].[Product]
         WHERE Name like '%XL%');
清單9:使用子查詢將值傳遞給IN關鍵字

清單9中的代碼使用一個子查詢從Product.Product表中返回不一樣的ProductID值,其名稱包含字符「XL」。 而後在IN關鍵字中使用從子查詢返回的這些ProductID值來約束從Sales.SalesOrderDetail表返回哪些行。

在修改數據的語句中使用子查詢的示例

到目前爲止,個人全部示例一直在演示如何在SELECT語句的不一樣部分中使用子查詢。 也能夠在INSERT,UPDATE或DELETE語句中使用子查詢。 清單10中的代碼顯示瞭如何在INSERT語句中使用子查詢。

DECLARE @SQTable TABLE (
OrderID int,
OrderDate datetime,
TotalDue money,
MaxOrderDate datetime);

-- INSERT with SubQuery
INSERT INTO @SQTable 
   SELECT SalesOrderID,
          OrderDate, 
          TotalDue, 
          (SELECT MAX(OrderDate) 
           FROM [Sales].[SalesOrderHeader]) 
   FROM [Sales].[SalesOrderHeader]
   WHERE CustomerID = 29614;

-- Display Records
SELECT * FROM @SQtable;
清單10:INSERT語句中的子查詢

在清單10中的代碼中,我使用一個子查詢來計算要插入列MaxOrderDate的值。 這只是在INSERT語句中如何使用子查詢的一個示例。 請記住,也能夠在UPDATE和/或DELETE語句中使用子查詢。

子查詢和JOIN之間的性能考慮

若是您已閱讀由Microsoft生成的「子查詢基礎知識」文檔(http://technet.microsoft.com/...),那麼您可能已經在此語句中運行 包含子查詢的語句的性能:

「在Transact-SQL中,包含子查詢的語句和不具備語義類似的版本的語句一般沒有性能差別。

要將使用子查詢的查詢的性能與不使用子查詢的等效查詢進行比較,我將在清單3中重寫個人子查詢以使用JOIN操做。 清單11顯示了我重寫的JOIN查詢,至關於清單3中的查詢。

SELECT SOD.* 
FROM [Sales].[SalesOrderDetail] AS SOD
INNER JOIN 
[Production].[Product] AS P
ON SOD.ProductID = P.ProductID
WHERE P.Name = 'Long-Sleeve Logo Jersey, XL';
清單11:與清單3中的查詢至關的JOIN查詢

要比較使用子查詢的清單3中的查詢的性能和使用JOIN的清單11中的查詢,我將使用清單12中的代碼運行兩個查詢。

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

-- Listing 3 query
SELECT * FROM [Sales].[SalesOrderDetail]
WHERE ProductID = (SELECT ProductID 
                   FROM Production.Product
                    WHERE Name = 'Long-Sleeve Logo Jersey, XL'); 

-- Listing 11 query
SELECT SOD.* 
FROM [Sales].[SalesOrderDetail] AS SOD
INNER JOIN 
[Production].[Product] AS P
ON SOD.ProductID = P.ProductID
WHERE P.Name = 'Long-Sleeve Logo Jersey, XL';
清單12:測試清單3和清單4的性能代碼

在運行列表12中的代碼以後,我回顧了「SET STATISTICS」語句生成的消息。 經過查看統計信息,我發現這兩個查詢對SalesOrderDetail表都有3,309個邏輯讀取,對於Product表有兩個邏輯讀取,每一個使用31 ms的CPU。 另外我查看了SQL Server爲這兩個查詢建立的執行計劃。 我發現SQL Server爲二者生成了相同的執行計劃。 所以,對於個人狀況使用子查詢或JOIN查詢產生了等效的性能,正如微軟所記錄的那樣。

總結

子查詢是嵌入另外一個Transact-SQL語句的SELECT語句。子查詢能夠獨立於外部查詢運行,所以有時也稱爲獨立查詢。記住,任什麼時候候你有一個子查詢代替一個表達式,或者與比較運算符一塊兒使用,它只能返回一個列和值。一般可使用JOIN邏輯重寫子查詢。子查詢是幫助您構建更復雜的Transact-SQL語句以知足業務需求的強大工具。

問題和答案

在本節中,您能夠經過回答如下問題來查看您使用子查詢概念瞭解的內容。

問題1:

完成這個句子「一個子查詢是另外一個Transact-SQL語句中的SELECT語句,_____________________」。

  • 不能獨立於完整的查詢運行。
  • 引用來自外部查詢的列。
  • 當獨立於外部查詢運行時,它將返回結果。

問題2:

何時子查詢只須要一個列和值才能返回(選擇全部適用的)?

  • 當子查詢用於FROM子句時
  • 當IN子句中使用子查詢時
  • 當表達式中使用子查詢時
  • 當子查詢與比較運算符一塊兒使用時

問題3:

在WHERE子句中使用一個子查詢的Transact-SQL語句老是比不包含子查詢(True或False)的等效查詢執行得慢。

回答:

問題1:

正確的答案是c。子查詢能夠獨立於外部查詢運行,並返回結果。它不須要來自外部查詢的任何列,若是它有來自外部查詢的列,它將被稱爲相關子查詢。

問題2:

正確的答案是c和d。當用做表達式或在比較操做中時,子查詢須要返回一個列值。當子查詢與IN關鍵字一塊兒使用時,它能夠返回列的單個或多個值。若是在FROM子句中使用子查詢,它只能返回一列和一個值,但也能夠返回多個列和值。

問題3:

正確答案是錯誤的。 SQL Server優化器很是聰明,極可能爲兩個等效查詢計算相同的執行計劃。若是包含子查詢的查詢的執行計劃和沒有子查詢的查詢的執行計劃最終都具備相同的執行計劃,則兩個查詢將具備相同的性能。

相關文章
相關標籤/搜索