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优化器很是聪明,极可能为两个等效查询计算相同的执行计划。若是包含子查询的查询的执行计划和没有子查询的查询的执行计划最终都具备相同的执行计划,则两个查询将具备相同的性能。

相关文章
相关标签/搜索