码迷,mamicode.com
首页 > 数据库 > 详细

翻译:Gregory Larsen,2016/02/19(第一版:2014年12月17日)高级T-SQL阶梯1级:使用CROSS JOIN介绍高级T-SQL

时间:2017-11-08 00:44:10      阅读:253      评论:0      收藏:0      [点我收藏+]

标签:连接   应该   1.0   个数   tin   删除   关联   目的   数据集   

原文链接:http://www.sqlservercentral.com/articles/Stairway+Series/119933/

原文作者:Gregory Larsen2016/02/19(第一版:20141217日)

 

系列

 

本文是Stairway SeriesStairway to Advanced T-SQL”的一部分

 

这个阶梯将包含一系列文章,这些文章将在前面两个T-SQL阶梯,T-SQL DMLT-SQL超越基础知识的T-SQL基础上进行扩展。 这个楼梯应该帮助读者准备通过Microsoft认证考试70-461:查询Microsoft SQL Server 2012

 

这是新阶梯系列中的第一篇文章,将探讨Transact SQLTSQL)的更多高级功能。 这个楼梯将包含一系列文章,这些文章将扩展到您在之前的两个TSQL楼梯中学到的TSQL基础:

 

1.T-SQL DML的阶梯

 

2.T-SQL的阶梯:超越基础

 

这个“高级Transact SQL”阶梯将涵盖以下TSQL主题:

 

1.使用CROSS JOIN运算符

 

2.使用APPLY运算符

 

3.了解公用表表达式(CTE

 

4.使用Transact-SQL游标进行记录级别处理

 

5.使用PIVOT在其一侧转换数据

 

6.使用UNPIVOT将列转成行

 

7.使用排名函数排序数据

 

8.用函数管理日期和时间

 

9.了解OVER子句的变化

 

这个阶梯的读者应该已经很好的理解了如何从SQL Server表中查询,更新,插入和删除数据。 另外,他们应该具有可用于控制其TSQL代码流的方法的工作知识,以及能够测试和操作数据。

 

这个楼梯应该帮助读者准备通过Microsoft认证考试70-461:查询Microsoft SQL Server 2012

 

对于这个新的阶梯系列的第一部分,我将讨论CROSS JOIN操作符。

 

CROSS JOIN操作符介绍

 

CROSS JOIN操作符可用于将一个数据集中的所有记录组合到另一个数据集中的所有记录中。 通过在两组记录之间使用CROSS JOIN运算符,您可以创建所谓的笛卡尔积。

 

下面是使用CROSS JOIN运算符连接两个表AB的简单示例:

 

SELECT * FROM A CROSS JOIN B

请注意,使用CROSS JOIN运算符时,没有连接两个表的连接子句,就像在两个表之间执行INNEROUTER JOIN操作时所使用的那样。

你需要知道使用CROSS JOIN可以产生一个大的记录集。 为了探索这种行为,我们来看看CROSS JOIN操作产生的集合有多大的两个不同的例子。 对于第一个示例,假设您交叉连接两个表,其中表A10行,而表B3行。 CROSS JOINresulti集合将是10330行的10倍。 对于第二个例子,假设表A1000万行,而表B300万行。 表AB之间的CROSS JOIN结果集中有多少行? 那将是高达300,000,000,000,000行。 这是很多行,它将花费大量的时间和大量的资源来创建结果集。 因此,在大型记录集上使用CROSS JOIN操作符时需要小心。

让我们仔细研究一下使用CROSS JOIN操作符的例子。

使用CROSS JOIN的基本示例

 

对于前两个例子,我们将加入两个样本表。 清单1中的代码将用于创建这两个示例表。 确保在用户数据库中运行这些脚本,而不是在主服务器上运行。

CREATE TABLE Product (ID int, 

                      ProductName varchar(100),

                      Cost money);CREATE TABLE SalesItem (ID int, 

                        SalesDate datetime, 

                        ProductID int, 

                        Qty int, 

                        TotalSalesAmt money);INSERT INTO Product

VALUES (1,‘Widget‘,21.99),

      (2,‘Thingamajig‘,5.38), 

  (3,‘Watchamacallit‘,1.96);INSERT INTO SalesItem

VALUES (1,‘2014-10-1‘,1,1,21.99),

      (2,‘2014-10-2‘,3,1,1.96),

      (3,‘2014-10-3‘,3,10,19.60),

      (4,‘2014-10-3‘,1,2,43.98),

      (5,‘2014-10-3‘,1,2,43.98); 

清单1CROSS JOIN的示例表

 

对于第一个CROSS JOIN示例,我将运行清单2中的代码。

SELECT * FROM 

Product CROSS JOIN SalesItem;

清单2:简单的CROSS JOIN示例

当我在SQL Server Management Studio窗口中运行清单2中的代码时,使用会话设置来输出文本结果,我在Report 1中得到了输出结果:

ID  ProductName           Cost     ID   SalesDate               ProductID Qty  TotalSalesAmt

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

1    Widget               21.99    1    2014-10-01 00:00:00.000 1         1    21.99

1    Widget               21.99    2    2014-10-02 00:00:00.000 3         1    1.96

1    Widget               21.99    3    2014-10-03 00:00:00.000 3         10   19.60

1    Widget               21.99    4    2014-10-03 00:00:00.000 1         2    43.98

1    Widget               21.99    5    2014-10-03 00:00:00.000 1         2    43.98

2    Thingamajig          5.38     1    2014-10-01 00:00:00.000 1         1    21.99

2    Thingamajig          5.38     2    2014-10-02 00:00:00.000 3         1    1.96

2    Thingamajig          5.38     3    2014-10-03 00:00:00.000 3         10   19.60

2    Thingamajig          5.38     4    2014-10-03 00:00:00.000 1         2    43.98

2    Thingamajig          5.38     5    2014-10-03 00:00:00.000 1         2    43.98

3    Watchamacallit       1.96     1    2014-10-01 00:00:00.000 1         1    21.99

3    Watchamacallit       1.96     2    2014-10-02 00:00:00.000 3         1    1.96

3    Watchamacallit       1.96     3    2014-10-03 00:00:00.000 3         10   19.60

3    Watchamacallit       1.96     4    2014-10-03 00:00:00.000 1         2    43.98

3    Watchamacallit       1.96     5    2014-10-03 00:00:00.000 1         2    43.98

 报告1:运行清单2时的结果

如果您在报告1中查看结果,则可以看到有15个不同的记录。 前5条记录包含Product表的第一行的列值,并与SalesItem表中的5个不同的行连接。 产品表的2秒和3行也是如此。 返回的行总数是Product表中的行数乘以SalesItem表中的行数,即15行。

为什么创建笛卡尔产品可能有用的一个原因是生成测试数据。 假设我想在ProductSalesItem表中使用日期来生成许多不同的产品。 我可以使用CROSS JOIN来做到这一点,如清单3所示:

SELECT ROW_NUMBER() OVER(ORDER BY ProductName DESC) AS ID,

       Product.ProductName

  + CAST(SalesItem.ID as varchar(2)) AS ProductName, 

       (Product.Cost / SalesItem.ID) * 100 AS CostFROM Product CROSS JOIN SalesItem;

清单3:简单的CROSS JOIN示例

当我运行清单3中的代码时,我得到了Report 2中的输出。

ID    ProductName                                                 Cost

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

1     Widget1                                                     2199.00

2     Widget2                                                     1099.50

3     Widget3                                                     733.00

4     Widget4                                                     549.75

5     Widget5                                                     439.80

6     Watchamacallit1                                             196.00

7     Watchamacallit2                                             98.00

8     Watchamacallit3                                             65.33

9     Watchamacallit4                                             49.00

10    Watchamacallit5                                             39.20

11    Thingamajig1                                                538.00

12    Thingamajig2                                                269.00

13    Thingamajig3                                                179.33

14    Thingamajig4                                                134.50

15    Thingamajig5                                                107.60

报告2:运行清单3时的结果

正如您通过查看清单3中的代码所看到的,我生成了一些包含与我的Product表中的数据类似的数据的行。 通过使用ROW_NUMBER函数,我能够在每一行上生成一个唯一的ID列。 此外,我使用我的SalesItem表中的ID列来创建唯一的ProductNameCost列值。 产生的行数等于Product表中的行数乘以SalesItem表中的行数。

到目前为止,该部分中的示例仅在两个表中执行了CROSS JOIN。 您可以使用CROSS JOIN运算符跨多个表执行CROSS JOIN操作。 清单4中的示例在三个表中创建了一个Cartesian产品。

SELECT * FROM sys.tables CROSS JOIN sys.objectsCROSS JOIN sys.sysusers;

清单4:使用CROSS JOIN运算符创建三个表的笛卡尔积

运行清单4的输出有两个不同的CROSS_JOIN操作。 从此代码创建的笛卡尔积将生成一个结果集,其总行数等于sys.tables中的行数,sys.objects中的行数乘以sys.sysusers中的行数。

CROSS JOININNER JOIN一样执行时

在前面的章节中,我提到当你使用CROSS JOIN操作符时,它将产生一个笛卡尔积。 这一直是不正确的。 当您使用限制CROSS JOIN操作中涉及的表的连接的WHERE子句时,SQL Server不会创建笛卡尔积。 相反,它的功能就像一个正常的JOIN操作。 为了演示这种行为,请查看清单5中的代码。

SELECT * FROM Product P CROSS JOIN SalesItem S

WHERE P.ID = S.ProductID;

SELECT * FROM Product P INNER JOIN SalesItem S

 

ON P.ID = S.ProductID;

清单5:两个相等的SELECT语句。

清单5中的代码包含两个SELECT语句。 第一个SELECT语句使用CROSS JOIN运算符,然后使用WHERE子句定义如何连接CROSS JOIN操作中涉及的两个表。 第二个SELECT语句使用带有ON子句的普通INNER JOIN运算符来加入这两个表。 SQL Server的查询优化器非常聪明,知道清单5中的第一个SELECT语句可以重写为INNER JOIN。 当CROSS JOIN操作与提供CROSS JOIN中涉及的两个表之间的连接谓词的WHERE子句结合使用时,优化器知道它可以重新编写查询。 因此,SQL Server引擎为清单5中的两个SELECT语句生成相同的执行计划。当您不提供WHERE约束时,SQL Server不知道如何连接涉及CROSS JOIN操作的两个表,因此会创建笛卡尔积 在与CROSS JOIN操作相关的两组之间。

使用CROSS JOIN查找未售出的产品

 

前面几节中的示例是帮助您了解CROSS JOIN运算符以及如何使用它。 使用CROSS JOIN操作符的功能之一就是使用它来帮助查找一个表中没有另一个表中匹配记录的项目。 例如,假设我要报告我的产品表中每个产品名称的总数量和总销售额,以表示我的任何一个产品项目的销售日期。 由于在我的例子中,每一个ProductName不是每天都有一个销售,所以我的报告要求意味着我需要为那些在某一天没有销售的产品显示数量0和总销售额$ 0。 这是CROSS JOIN操作符与LEFT OUTER JOIN操作一起使用的地方,可以帮助我识别那些在某一天没有销售的物品。 清单6列出了满足这些报告要求的代码:

SELECT S1.SalesDate, ProductName

     , ISNULL(Sum(S2.Qty),0) AS TotalQty

, ISNULL(SUM(S2.TotalSalesAmt),0) AS TotalSales

FROM Product P

CROSS JOIN  

(

SELECT DISTINCT SalesDate FROM SalesItem

  ) S1

LEFT OUTER JOIN 

SalesItem S2

ON P.ID = S2.ProductID

AND S1.SalesDate = S2.SalesDate

GROUP BY S1.SalesDate, P.ProductName

 

ORDER BY S1.SalesDate;

清单6:查找不使用CROSS JOIN销售的产品

让我引导你通过这个代码。我创建一个子查询,选择所有不同的SalesDate值。这个子查询给我所有的销售日期。然后我与我的产品表CROSS JOIN。这使我可以在每个SalesDate和每个Product行之间创建一个Cartesian产品。从CROSS JOIN返回的集合将在最终结果集中包含我需要的每个值,但每个销售的产品的数量和TotalSalesAmt之和除外。为了得到这些汇总值,我对SalesItem表执行LEFT OUTER JOIN,把它与我用CROSS JOIN操作创建的笛卡尔积相连。我基于ProductIDSalesDate列执行了此连接。通过使用LEFT OUTER JOIN,我的笛卡尔积中的每一行都会被返回,如果ProductIDSalesDate有一个匹配的SalesDate记录,则QtyTotalSalesAmt值将与相应的行关联。这个查询所做的最后一件事是使用GROUP BY子句来总结基于SalesDateProductNameQtyTotalSalesAmount

 

性能考虑

生产笛卡尔产品的CROSS JOIN算子有一些性能方面需要考虑。 由于SQL引擎需要在一组中的每一行中连接另一组中的每一行,因此结果集可能相当大。 如果我使用另一个具有100,000行的表进行CROSS JOIN一个具有1,000,000行的表,那么结果集将具有1,000,000 X 100,000行或100,000,000,000行。 这是一个很大的结果集,它将花费大量的时间来创建它。

 

CROSS JOIN操作员可以成为一个很好的解决方案,用于识别两套所有可能组合的结果集,例如每个月所有客户的销售额,即使在某些月份某些客户没有销售额。 使用CROSS JOIN操作符时,如果要优化性能,应尽量减少正在交叉连接的组的大小。 例如,假设我有一张包含过去2个月销售数据的表格。 如果我想生成一个报告,显示一个月内没有任何销售的客户,那么确定一个月中的天数可以彻底改变我的查询的性能。 为了证明这一点,我首先为两个月的时间为1,000位客户创建一组销售记录。 我将使用清单7中的代码执行此操作。

CREATE TABLE Cust (Id int, CustName varchar(20));

CREATE TABLE Sales (Id int identity

                    ,CustID int

,SaleDate date

,SalesAmt money);SET NOCOUNT ON;DECLARE @I int = 0;DECLARE @Date date;WHILE @I < 1000

BEGIN

SET @I = @I + 1;

SET @Date = DATEADD(mm, -2, ‘2014-11-01‘);

INSERT INTO Cust

VALUES (@I, 

       ‘Customer #‘ + right(cast(@I+100000 as varchar(6)),5));

WHILE @Date < ‘2014-11-01‘ 

BEGIN

IF @I%7 > 0

INSERT INTO Sales (CustID, SaleDate, SalesAmt) 

VALUES (@I, @Date, 10.00);

SET @Date = DATEADD(DD, 1, @Date);

ENDEND

清单7TSQL为性能测试创建示例数据

清单7中的代码为1,000个不同的客户创建了2个月的数据。 此代码不会为每个第7个客户添加销售数据。 此代码生成1,000Cust表记录和52,338个销售表记录。

为了演示如何使用CROSS JOIN操作符根据CROSS JOIN输入集中使用的集合的大小执行不同的操作,让我运行清单8和清单9中的代码。对于每个测试,我将记录返回结果。

SELECT CONVERT(CHAR(6),S1.SaleDate,112) AS SalesMonth, C.CustName, 

       ISNULL(SUM(S2.SalesAmt),0) AS TotalSales

FROM Cust C

CROSS JOIN  

(

SELECT SaleDate FROM Sales

) AS S1

LEFT OUTER JOIN 

Sales  S2

ON C.ID = S2.CustID

AND S1.SaleDate = S2.SaleDate

GROUP BY CONVERT(CHAR(6),S1.SaleDate,112),C.CustNameHAVING ISNULL(SUM(S2.SalesAmt),0) = 0

ORDER BY CONVERT(CHAR(6),S1.SaleDate,112),C.CustName

清单8:针对所有销售记录的CROSS JOIN

SELECT CONVERT(CHAR(6),S1.SaleDate,112) AS SalesMonth, C.CustName, 

       ISNULL(SUM(S2.SalesAmt),0) AS TotalSales

FROM Cust C

CROSS JOIN  

(

SELECT DISTINCT SaleDate FROM Sales

) AS S1

LEFT OUTER JOIN 

Sales  S2

ON C.ID = S2.CustID

AND S1.SaleDate = S2.SaleDate

GROUP BY CONVERT(CHAR(6),S1.SaleDate,112),C.CustName

HAVING ISNULL(SUM(S2.SalesAmt),0) = 0

ORDER BY CONVERT(CHAR(6),S1.SaleDate,112),C.CustName

清单9CROSS JOIN针对不同的销售日期列表

在清单8中,CROSS JOIN运算符将1,000Cust记录与52,338个销售记录结合在一起,以产生52,338,000行记录集,然后用于确定一个月内销售额为零的客户。在清单9中,我将我的选择标准从我的Sales表中更改为只返回一组不同的SalesDate值。这个不同的集合只产生61个不同的SalesDate值,所以清单9中的CROSS JOIN操作的结果只产生了61,000条记录。通过减少CROSS JOIN操作的结果集,我的清单9中的查询运行时间不到1秒,而清单8中的代码在我的机器上运行了19秒。造成这种性能差异的主要原因是SQL Server需要为每个查询执行的不同操作处理大量记录。如果你看看这两个列表的执行计划,你会发现计划略有不同。但是,如果查看从嵌套循环(Inner Join)操作生成的记录的估计数量,则在图形计划的右侧,您将看到清单8估计有52,338,000条记录,而清单9中的相同操作估计只有61,000条记录。清单8的查询计划从CROSS JOIN嵌套循环操作生成的这个大记录集然后传递给几个额外的操作。 因为清单8中的所有这些操作都需要对5200万条记录进行操作。 清单8比清单9慢得多。

正如您所看到的,在CROSS JOIN操作中使用的记录数量会显着影响查询运行的时间长度。 因此,如果您可以编写查询以最大限度地减少CROSS JOIN操作中涉及的记录数,那么您的查询将会更加高效。

结论

CROSS JOIN操作符在两个记录集之间生成笛卡尔积。 此操作符在帮助识别一个表中没有另一个表中的匹配记录的项目时非常有用。 应小心使用CROSS JOIN操作符所使用的记录集的大小。 通过确保CROSS JOIN的结果集尽可能小,您将确保您的代码尽可能快地运行。

问题和答案

在本节中,您可以通过回答以下问题来查看使用CROSS JOIN操作符的理解程度。

问题1

CROSS JOIN运算符通过基于ON子句中指定的列匹配两个记录集来创建一个结果集。(对或错

a.

b.

问题2

当表AB包含重复行时,可以使用哪个公式来标识将从两个表AB之间的不受约束的CROSS JOIN返回的行数?

a.表中的行数A乘以表B中的行数

b.A中的行数A乘以表B中的唯一行数

c.A中的唯一行数A乘以表B中的行数

d.B中的唯一行数A乘以表B中的唯一行数

问题3

哪种方法最有可能减小CROSS JOIN操作产生的笛卡尔积?

a.确保两组被连接的行数尽可能多

b.确保连接的两组尽可能少

c.确保CROSS JOIN操作左侧的行数尽可能少

d.确保CROSS JOIN操作右侧的行数尽可能少

回答:

问题1

正确答案是bCROSS JOIN运算符不使用ON子句执行CROSS JOIN操作。 它将一个表中的每一行连接到另一个表中的每一行。 CROSS JOIN在连接两组时创建一个笛卡尔积。

问题2

正确的答案是abcd是不正确的,因为如果表AB中有重复的行,则在为CROSS JOIN操作创建笛卡尔积时,每个重复行都是连接。

问题3

正确答案是b。 通过减少在CROSS JOIN操作中涉及的两个集合的大小,使由CROSS JOI操作创建的最终集合的大小最小化。 cd也有助于减小由CROSS JOIN操作创建的最终集合的大小,但并不像确保CROSS JOIN操作中涉及的两个集合具有尽可能最少的行那样最佳。

 

本文是高级T-SQL阶梯的一部分。

 

 

技术分享

翻译:Gregory Larsen,2016/02/19(第一版:2014年12月17日)高级T-SQL阶梯1级:使用CROSS JOIN介绍高级T-SQL

标签:连接   应该   1.0   个数   tin   删除   关联   目的   数据集   

原文地址:http://www.cnblogs.com/705xinguan/p/7801975.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!