T-SQL进阶:超越基础 Level 2:编写子查询

By Gregory Larsen, 2016/01/01 (首次发布于: 2014/01/29)

关于系列

本文属于进阶系列:T-SQL进阶:超越基础

跟随Gregory Larsen的T-SQL DML进阶系列,其涵盖了更多的高级方面的T-SQL语言,如子查询。

在您开始创建超出基本Transact-SQL语句的更复杂的SQL代码时,您可能会发现需要使用其他SELECT语句的结果来限制查询。 当在父Transact-SQL语句中嵌入SELECT语句时,这些嵌入式SELECT语句被称为子查询或相关子查询。 在“超越基础”楼梯的这个层次上,我将讨论一个子查询的不同方面,在将来的一个层面上,我将讨论相关的子查询。

什么是子查询?

子查询只是一个SELECT语句,它包含在另一个Transact-SQL语句中。可以在任何可以使用表达式的地方使用子查询。许多子查询返回单个列值,因为它们与比较运算符(=,!=,<,<=,>,> =)或表达式结合使用。当子查询不用作表达式或使用比较运算符时,它可以返回多个值。此外,子查询甚至可以在FROM子句或关键字EXISTS中使用时返回多个列和值。

子查询容易在Transact-SQL语句中发现,因为它将是括号中的SELECT语句。由于子查询包含在Transact-SQL语句中,因此子查询通常称为内部查询。而包含子查询的Transact-SQL语句被称为外部查询。子查询的另一个特点是可以独立于外部查询运行,并且将无错误地运行,并且可能返回一组行或空行集。

子查询的另一种形式是相关子查询。但是相关的子查询不能独立于外部的Transact SQL语句运行。相关子查询使用外部查询中的列或列来约束从相关子查询返回的结果。这对于本文的相关子查询足够了。我将在未来的楼梯文章中探索相关的子查询。

使用子查询时还需要考虑以下几点:

  • ntext,text和image数据类型不允许从子查询返回
  • ORDER BY子句不能用于子查询,除非使用TOP操作符
  • 使用子查询的视图无法更新
  • COMPUTE和INTO子句不能在子查询中使用

子查询示例数据示例

为了演示如何使用子查询,我将需要一些测试数据。 而不是创建自己的测试数据,我的所有示例都将使用AdventureWorks2008R2数据库。 如果您想跟随并在环境中运行我的示例,那么您可以从这里下载AdventureWorks2008R2数据库:http://msftdbprodsamples.code...

返回单个值的子查询的示例

如上所述,在表达式中使用的子查询或返回比较运算符一侧的值需要返回单个值。 Transact-SQL语句中有许多不同的地方,需要一个子查询来返回单个列值,例如在选择列表中WHERE子句等。在本节中,我将提供一系列示例,演示如何使用子查询 作为表达式或与比较运算符以满足不同的业务需求。

列列表中的子查询

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏青玉伏案

Oracle之PL/SQL学习笔记

  自己在学习Oracle是做的笔记及实验代码记录,内容挺全的,也挺详细,发篇博文分享给需要的朋友,共有1w多字的学习笔记吧。是以前做的,一直在压箱底,今天拿出...

2058
来自专栏小灰灰

mysql之基本语法

本篇将主要集中在mysql的使用上,包括如何创建标,如何进行insert,update,select,delete,以及一些常见的sql中关键字的使用姿势

63222
来自专栏数据库

mysql数据操作语句

?imageMogr2/blur/1x0/quality/75|watermark/1/image/aHR0cDovL29zNzhmNGhueS5ia3QuY2...

1995
来自专栏Linyb极客之路

MySQL索引设计不可忽视的知识点

本文主要讨论MySQL索引的部分知识。将会从MySQL索引基础、索引优化实战和数据库索引背后的数据结构三部分相关内容,下面一一展开。

1014
来自专栏aoho求索

Mysql探索(一):B-Tree索引

MySQL是目前业界最为流行的关系型数据库之一,而索引的优化也是数据库性能优化的关键之一。所以,充分地了解MySQL索引有助于提升开发人员对MySQL数据库的使...

1251
来自专栏程序员历小冰

Mysql探索(一):B-Tree索引

MySQL是目前业界最为流行的关系型数据库之一,而索引的优化也是数据库性能优化的关键之一。所以,充分地了解MySQL索引有助于提升开发人员对MySQL数...

2153
来自专栏资深Tester

增删改查的增删改

2464
来自专栏自由而无用的灵魂的碎碎念

分享:Oracle sql语句优化

最近做查询时,写的一条查询语句用了两个IN,导致tuexdo服务积压了不少,用户没骂就不错了。最后经过技术经理的点拨,sql语句性能提升了大约10倍,主要用了表...

1841
来自专栏友弟技术工作室

工作中数据库优化技巧

内容整理于网络 一、EXPLAIN 做MySQL优化,我们要善用 EXPLAIN 查看SQL执行计划。 EXPLAIN 输出格式 EXPLAIN 命令的输出内容...

34011
来自专栏个人随笔

MySQL 关于索引那点事

索引 其实数据库中的数据是按页存放的 其实索引也是按页存放的 所以本质上索引也占硬盘空间(以最小的消耗,换取最大的利益) 索引是一种有效组合数据的方式!为快速...

2899

扫码关注云+社区

领取腾讯云代金券