四巨头第十周作业翻译

通往T-SQL的阶梯:超越基础3级:构建相关的子查询

格雷戈里·拉森,2014/03/05

 

此系列

这篇文章是阶梯系列的一部分:T-SQL的阶梯:超越基础。

从他的阶梯到T-SQL DML,格雷戈里·拉森涵盖了T-SQL语言的更高级的方面,比如子查询。

在这个阶梯的第2级,我讨论了如何在Transact-SQL语句中使用子查询。通过讨论一种称为关联子查询的子查询类型,这个楼梯级别将扩展到子查询主题。我将探讨什么是相关子查询,以及它与普通子查询的区别。此外,我还将提供一些超越基础的事务- sql语句示例,并使用相关子查询来帮助识别结果集中返回的行,以满足复杂的业务需求。

什么是相关子查询?

在这段阶梯的第2级,我们了解到一个正常的子查询只是另一个Transact-SQL语句中的一个SELECT语句,在这个语句中,如果独立于外部查询,子查询可以返回结果。相关子查询是子查询的一种形式,它不能独立于外部查询运行,因为它包含来自外部查询的一个或多个列。相关子查询,就像普通的子查询一样,有时被称为内部查询。如果相关子查询(内部查询)是独立于外部查询运行的,那么它将返回一个错误。因为内部查询的执行依赖于来自外部查询的值,因此它被称为相关子查询。

相关子查询可以执行多次。它将为在外部查询中选择的每个候选行运行一次。每个候选行的列值将用于为关联子查询的每个执行内部的外部查询列提供值。包含相关子查询的语句的最终结果将基于相关子查询的每个执行结果。

相关子查询示例的示例数据

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

在WHERE子句中关联子查询的示例

为了演示在WHERE子句中使用相关子查询,我想要确定那些以单个订单购买了超过70个项目的CustomerID。为了完成这一要求,我运行清单1中的代码。

SELECT CustomerID

FROM Sales.SalesOrderHeader OH

WHERE (SELECT COUNT(*)

 FROM Sales.SalesOrderDetail

               WHERE SalesOrderID = OH.SalesOrderID) > 70;

清单1:WHERE子句中的相关子查询。

当我运行清单1中的代码时,我得到了报告1中的输出。

CustomerID
-----------
29712
29722
30048
30107

报告1:运行清单1中的代码时返回的结果。

如果你查看清单1中的代码,你将看到我使用相关子查询限制了我的位置。子查询是圆括号中的代码,我从清单1中提取了相关的子查询代码,并将其放入清单2中。

SELECT COUNT(*) 
FROM Sales.SalesOrderDetail 
          WHERE SalesOrderID = OH.SalesOrderID

清单2:清单1中的子查询代码。

如果我运行清单2中的代码,我将发现在报告2中显示的错误。

Msg 4104, Level 16, State 1, Line 3
The multi-part identifier "OH.SalesOrderID
" could not be bound.

报告2:运行清单2中的代码时出错。

我得到了报告2中显示的错误,因为关联子查询包含对列的引用。SalesOrderID,它是来自外部查询的一列。由于所有相关子查询都引用外部查询中的一个或多个列,所以不能独立地运行它们与它关联的外部查询。不能独立于整个Transact-SQL语句运行子查询的事实是,将相关子查询与普通子查询区分开来。

这里给出的示例是在WHERE子句中使用相关子查询的一个非常简单的示例。希望通过这样一个简单的示例,可以很容易地理解正常子查询和相关子查询之间的区别。通常,相关的子查询可能要复杂得多。此外,请记住,在不使用相关子查询的情况下,可能还有其他方法可以满足业务需求。

正如你所看到的,编写相关子查询与普通子查询非常相似,但是不能独立地运行相关子查询。

在HAVING子句中相关子查询的例子。

有时,您可能希望通过来自外部查询的不同值来约束一个包含子句。这是在您的HAVING子句中可以使用相关子查询的时候。假设您必须编写一个查询,计算那些在2008年之前购买了超过15万美元的产品的客户的退款金额。清单3中的代码通过在HAVING子句中使用相关子查询来计算这些值客户的折扣金额。

SELECT Outer_H.[CustomerID]

     , SUM(Outer_H.[SubTotal]) AS TotalPurchase

                , SUM(Outer_H.[SubTotal]) * .10 AS Rebate

FROM [Sales].[SalesOrderHeader] AS Outer_H

WHERE YEAR(Outer_H.[OrderDate]) = '2008'

GROUP BY Outer_H.[CustomerID]

HAVING (SELECT SUM(Inner_H.[SubTotal]) FROM [Sales].[SalesOrderHeader] AS Inner_H

        WHERE Inner_H.[CustomerID] = Outer_H.[CustomerID]

                               AND YEAR(Inner_H.[OrderDate]) = '2008') > 150000

ORDER BY Rebate DESC;

清单3:HAVING子句中的相关子查询。

当我运行清单5中的代码时,我得到了报告3中的结果。

CustomerID  TotalPurchase         Rebate

----------- --------------------- ---------------------------------------

29923       220496.658            22049.665800

29641       210647.4929           21064.749290

29617       187964.844            18796.484400

29913       186387.5613           18638.756130

29818       179916.2877           17991.628770

29940       175358.3954           17535.839540

29987       172169.4612           17216.946120

29736       157700.6034           15770.060340

29995       156984.5148           15698.451480

29770       151824.9944           15182.499440

报告3:运行清单3的结果。

清单3中的相关子查询代码在相关子查询中的外部查询中使用GROUP BY子句中的CustomerID。关联子查询将被执行一次,每次从GROUP BY子句返回。这允许有条款来计算从外部查询中向每个CustomerID销售的产品的总数量,通过在每个SalesOrderHeader记录上的SubTotal列的值相加,其中记录与来自外部查询的CustomerID相关联。清单3中的Transact-SQL语句只返回了CustomerID已经购买了价值超过15万美元的产品的一行。

包含相关子查询的UPDATE语句的示例。

相关子查询不仅可以使用SELECT语句返回结果集。您还可以使用它们来更新SQL Server表中的数据。为了演示这一点,我将首先使用清单4中的代码,在tempdb表中生成一些测试数据。

USE tempdb;

GO

SET NOCOUNT ON;

CREATE TABLE CarInventory (

ID int identity,

CarName varchar(50),

VIN varchar(50),

StickerPrice decimal (7,2),

InvoicePrice decimal (7,2));

GO

INSERT INTO CarInventory VALUES ('Explorer','EXP2014123456A',46198.45,38201.87),

('Explorer','EXP2014123493A',47129.98, 38201.87),                              

('Grand Cherokee','JGC20141234345X',41678.45,36201.86),

('Grand Cherokee','JGC20141234556W',44518.31,36201.86),

('Pathfinder','NPF2014987365A',32587.73,28917.10),

('Pathfinder','NPF2014239657B',33577.54,28917.10),

('Pathfinder','NPF2014098587C',35876.12,28917.10),

('Tahoe','TAH201409674A',52001.08,46000.01);

清单4:创建和填充测试表的代码。

清单4中的代码创建了一个CarInventory表,然后填充了8行,表示当前库存中的汽车。

销售经理周期性地通过运行清单5中的查询来查看他的InvoicePriceRatio。

SELECT CarName, InvoicePrice/StickerPrice*100.0 AS InvoicePriceRatio

FROM CarInventory;

清单5:InvoicePriceRatio查询

当管理人员运行这个查询时,她注意到有许多类似的汽车,其发票金额与发票金额不同。为了最大限度地提高她的发票价格,她要求她的IT支持写一个查询,以更新她所有汽车的粘着价格,这样每辆车都有相同的车名值,这是相同的发票。她想让IT男把StickerPrice设置为与CarName的最大标价相同的价格。这样,所有具有相同CarName值的汽车将具有相同的StickerPrice值。为了完成CarInventory表的更新,IT人员运行清单6中的Transact-SQL语句,其中包含一个相关的子查询。

UPDATE CarInventory 

SET StickerPrice = (SELECT MAX(StickerPrice)

                    FROM CarInventory Inner_CI

                    WHERE Inner_CI.CarName = Outer_CI.CarName) 

FROM CarInventory Outer_CI;

清单6:相关子查询,以将CarInventory更新为最大的标价。

清单8中的代码使用关联子查询中的外部查询的CarName来标识每个惟一的CarName的最大StickerPrice。然后,在相关子查询中发现的这个最大的StickerPrice值用于更新具有相同CarName的每个CarInventory记录的StickerPrice值。

相关子查询的性能考虑。

在编写包含相关子查询的Transact-SQL语句时,需要注意一些性能方面的考虑。当外部查询包含少量行时,性能并不差。但是,当外部查询包含大量的行时,从性能的角度来看,它的伸缩性并不好。这是因为在外部查询中每个候选行都需要执行相关的子查询。因此,当外部查询包含越来越多的候选行时,相关的子查询必须多次执行,因此Transact-SQL语句需要更长的时间运行。如果您发现相关子查询Transact-SQL语句的性能不符合您的要求,那么您应该寻找其他的解决方案,例如使用内部或外部连接操作的查询,或者从外部查询返回少量候选行的查询。

总结

相关子查询是一个内部查询,其中包含来自外部查询的一个或多个列。关联子查询对外部查询的每个候选行执行一次。因为相关子查询包含来自外部查询的列,因此它不能独立于外部查询运行。相关子查询有它们的位置,尽管在外部查询中识别出大量候选行时,从性能角度来看,它们的伸缩性并不好。

问题和答案

在本节中,您可以通过回答以下问题来回顾您如何理解相关子查询的概念。

问题1:

当编写一个相关的子查询时,您需要有___________________。(填入空白)

一个或多个来自内部查询的列,用于约束相关子查询的结果。

在相关子查询的选择列表中使用的内部查询中的一个或多个列。

来自外部查询的一个或多个列,用于约束相关子查询的结果。

在相关子查询的选择列表中使用的外部查询中的一个或多个列。

问题2:

选择所有关于相关子查询的声明。

随着候选行数的增加,包含相关子查询的Transact-SQL语句的性能得到提高。

关联子查询将为来自外部查询的每个候选行执行一次。

相关子查询将引用内部查询中的一个或多个列。

当在一个包含子句中使用相关子查询时,将为GROUP by子句返回的每个候选行执行一次内部查询。

问题3:

相关子查询就像一个普通的子查询,相关子查询可以独立于整个Transact-SQL语句(True或False)运行。

答案:

问题1:

正确答案是c。相关子查询要求在相关子查询语句中使用来自外部查询的一个或多个列。在执行相关子查询时,这些外部列引用被替换为每个候选行的值。

问题2:

正确的答案是b和d。a是不正确的,因为随着候选行的数量增加,相关子查询的执行次数会增加,而Transact-SQL语句的性能会变差。c是不正确的,因为相关子查询必须包含来自外部查询的一个或多个行,而不是内部查询。

问题3:

正确的答案是b。如果您试图独立于完整的Transact-SQL语句来运行相关的子查询,那么相关的子查询语句就会失败。