Gregory Larsen,2014 / 03 / 05
该系列 本文是系列的一部分:楼梯楼梯T-SQL:超越基础 下面就从他的楼梯T-SQL DML、Gregory Larsen覆盖的T-SQL语言如子查询更先进的方面。 在这楼梯2级我讨论如何在Transact-SQL语句中使用子查询。这楼梯水平将扩大对查询主题的讨论类型的查询称为相关子查询。我将探讨什么是相关子查询以及它是如何不同于普通的子查询。此外,我将提供你一些事务的SQL语句,超越基本的例子和使用相关子查询来帮助识别返回的行的结果集,以满足复杂的业务需求。 相关子查询是什么? 在这楼梯2级我们得知一个正常的子查询是一个SELECT语句,在另一个Transact-SQL语句,在子查询可以独立运行的外部查询返回结果。相关子查询是一种形式的子查询不能独立运行的外部查询,因为它包含了从外部查询的一个或多个列。相关子查询,就像一个正常的子查询,有时被称为内部查询。如果相关子查询(内查询)是独立运行的外部查询将返回一个错误。由于内部查询的执行依赖于外部查询的值,则称为相关子查询。 相关子查询可以执行多次。它将在外部查询中选择的每个候选行运行一次。每个候选人排列的值将被用来为外部查询栏目内的相关子查询每个执行提供值。一个声明中包含一个相关子查询将基于对相关子查询每个执行结果的最终结果。 相关子查询实例样本数据 为了演示如何使用相关子查询我需要一些测试数据。而不是创建自己的测试数据,我所有的例子将使用adventureworks2008r2数据库。如果你想跟着我,跑在您的环境中的例子你可以从这里下载adventureworks2008r2数据库:http://msftdbprodsamples.codeplex.com/releases/view/93587 在一个相关子查询的WHERE子句的例子 展示一个相关子查询的WHERE子句中使用的假设,我想找出那些CustomerID已经购买了70多个款式。为了完成这个需求,我可以运行代码:
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因为我的相关子查询包含一个参考列oh.salesorderid是从外部查询的列。由于所有相关子查询引用一个或多个列,从外部查询你不能独立于外部查询与之相关的。事实上,你不能运行查询独立于整个Transact-SQL语句的区别在于,相关子查询在一个正常的子查询。 这里给出的例子是使用相关子查询在WHERE子句中一个很平凡的例子。希望通过这样一个简单的例子很容易理解一个正常的子查询和相关子查询的区别。通常一个相关子查询可能会相当复杂。另外,记住有可能满足您的业务需求而不使用相关子查询的其他方法。 正如你可以看到,写一个相关子查询是一个正常的查询非常相似,但你就是不能运行相关子查询独立于外部查询。 在HAVING子句相关子查询的例子 有时您可能希望通过外部查询使用不同的值来约束有子句。这是当你可以在HAVING子句中使用相关子查询。假设你必须写一个查询,计算那些在2008年度前购买超过150000美元产品的顾客的回扣金额。清单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中的代码使用CustomerID从集团外部查询中的条款在相关子查询。相关子查询的执行将为每一行返回GROUP BY子句。这使得HAVING子句计算总金额的产品从外部查询卖给每个CustomerID的值求和,次全列每salesorderheader记录在记录与从外部查询相关信息。Transact-SQL语句在清单3中只返回一行,CustomerID在购买了价值超过150000美元的产品。
UPDATE语句包含一个相关子查询的例子 相关子查询不仅可以用于返回一个结果集,使用SELECT语句。还可以使用它们更新SQLServer表中的数据。为了证明这一点,我会先在tempdb表生成一些测试数据,通过使用清单4中的代码。
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表然后填充八行代表车当前库存。 定期的销售经理喜欢通过运行清单5中的查询看到他的invoicepriceratio。
SELECT CarName, InvoicePrice/StickerPrice*100.0 AS InvoicePriceRatio
FROM CarInventory;
清单5:invoicepriceratio查询 当经理运行此查询她注意到有一些相同的发票价量有不同的invoicepriceratio值类似的汽车。为了最大化自己的发票价格比她问她写一个查询,将更新所有她的车,每一辆车的StickerPrice同carname值具有相同的invoicepriceratio IT支持。她希望这家伙把StickerPrice作为carname最高标价相同的值。这样,所有的汽车都有相同的carname值都有相同的stickerprice价值。为了完成这个更新的carinventory表,这家伙的交易清单6中的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 Maximum Sticker Price 清单8中的代码使用在相关子查询的外部查询的carname确定每个独特的carname最大StickerPrice。这个最大的stickerprice价值在相关子查询发现,然后被用来更新stickerprice值为每个carinventory记录具有相同的carname。 相关子查询的性能考虑 有一些性能方面的考虑,你应该知道在编写Transact-SQL语句包含相关子查询。当外部查询包含少量行时,性能并不差。但是,当外部查询包含大量行时,它不会从性能角度很好地扩展。这是因为相关子查询需要外部查询中的每个候选行执行。因此,当外部查询包含越来越多的候选行相关子查询被执行多次,因此Transact-SQL语句将需要更长的时间来运行。如果你发现你的相关子查询的SQL语句的性能不能满足你的要求,那么你应该寻找替代解决方案,如使用内部或外部连接操作的查询,或是返回一个小数量的候选行从外部查询。 总结 相关子查询是一种内在的查询,包括从外部查询的一个或多个列。相关子查询被执行一次外部查询的每个候选行。因为一个相关子查询包含从外部查询不能列可独立运行的外部查询。相关子查询的地方,虽然没有规模从性能的角度来看,有大量的候选人排在外查询鉴定。