先进的T-SQL 1级楼梯:高级T-SQL使用交叉连接的介绍
作者:Gregory Larsen,2016/02/19(第一版:2014年12月17日)
该系列
本文是“Stairway Series:Stairway to Advanced T-SQL”的一部分
这个阶梯将包含一系列文章,这些文章将在前面两个T-SQL阶梯,T-SQL DML和T-SQL超越基础知识的T-SQL基础上进行扩展。这个楼梯应该帮助读者准备通过Microsoft认证考试70-461:查询Microsoft SQL Server 2012。
这是新阶梯系列中的第一篇文章,将探讨Transact SQL(TSQL)的更多高级功能。这个楼梯将包含一系列将在前两个TSQL楼梯上学习到的TSQL基础的文章:
T-SQL DML的阶梯
T-SQL的阶梯:超越基础
这个“高级Transact SQL”阶梯将涵盖以下TSQL主题:
使用CROSS JOIN运算符
使用APPLY运算符
了解公用表表达式(CTE)
使用Transact-SQL游标进行记录级别处理
使用PIVOT在其一侧转换数据
使用UNPIVOT将列转成行
使用排名函数排序数据
用函数管理日期和时间
了解OVER子句的变化
这个阶梯的读者应该已经很好的理解了如何从SQL Server表中查询,更新,插入和删除数据。另外,他们应该具有可用于控制其TSQL代码流的方法的工作知识,以及能够测试和操作数据。
这个楼梯应该帮助读者准备通过Microsoft认证考试70-461:查询Microsoft SQL Server 2012。
对于这个新的阶梯系列的第一部分,我将讨论CROSS JOIN操作符。
CROSS JOIN操作符介绍
CROSS JOIN操作符可用于将一个数据集中的所有记录组合到另一个数据集中的所有记录中。通过在两组记录之间使用CROSS JOIN运算符,您可以创建所谓的笛卡尔积。
以下是使用CROSS JOIN运算符连接两个表A和B的简单示例:
SELECT * FROM CROSS JOIN B
请注意,使用CROSS JOIN运算符时,没有连接两个表的连接子句,就像在两个表之间执行INNER和OUTER JOIN操作时所使用的那样。
你需要知道使用CROSS JOIN可以产生一个大的记录集。为了探索这种行为,我们来看看CROSS JOIN操作产生的集合有多大的两个不同的例子。对于第一个示例,假设您交叉连接两个表,其中表A有10行,而表B有3行。 CROSS JOIN的resulti集合将是10或3或30行的10倍。对于第二个例子,假设表A有1000万行,而表B有300万行。表A和B之间的CROSS JOIN结果集中有多少行?那将是高达300,000,000,000,000行。这是很多行,它将花费大量的时间和大量的资源来创建结果集。因此,在大型记录集上使用CROSS JOIN操作符时需要小心。
让我们仔细研究一下使用CROSS JOIN操作符的例子。
使用CROSS JOIN的基本示例
对于前两个例子,我们将加入两个样本表。清单1中的代码将用于创建这两个示例表。确保在用户数据库中运行这些脚本,而不是在主服务器上运行。
CREATE TABLE产品(ID int,
ProductName varchar(100),
成本钱);
CREATE TABLE SalesItem(ID int,
SalesDate datetime,
ProductID int,
数量int,
TotalSalesAmt钱);
插入产品
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);
清单1:CROSS JOIN的示例表
对于第一个CROSS JOIN示例,我将运行清单2中的代码。
选择*从
产品CROSS JOIN SalesItem;
清单2:简单的CROSS JOIN示例
当我在SQL Server Management Studio窗口中运行清单2中的代码时,使用会话设置来输出文本结果,我在Report 1中得到了输出结果:
编号产品名称成本编号销售日期产品编号数量总计销售额
--- --------------------- -------- ---- -------------- --------- --------- ---- ---------------
1小工具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小工具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行。
为什么创建笛卡尔产品可能有用的一个原因是生成测试数据。假设我想在Product和SalesItem表中使用日期来生成许多不同的产品。我可以使用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成本
FROM Product CROSS JOIN SalesItem;
清单3:简单的CROSS JOIN示例
当我运行清单3中的代码时,我得到了Report 2中的输出。
ID产品名称成本
----- --------------------------------------------- -------------- ---------------------
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
报告2:运行清单3时的结果
正如您通过查看清单3中的代码所看到的,我生成了一些包含与我的Product表中的数据类似的数据的行。通过使用ROW_NUMBER函数,我能够在每一行上生成一个唯一的ID列。此外,我使用我的SalesItem表中的ID列来创建唯一的ProductName和Cost列值。产生的行数等于Product表中的行数乘以SalesItem表中的行数。
到目前为止,该部分中的示例仅在两个表中执行了CROSS JOIN。您可以使用CROSS JOIN运算符跨多个表执行CROSS JOIN操作。清单4中的示例在三个表中创建了一个Cartesian产品。
SELECT * FROM sys.tables
CROSS JOIN sys.objects
CROSS JOIN sys.sysusers;
清单4:使用CROSS JOIN运算符创建三个表的笛卡尔积
运行清单4的输出有两个不同的CROSS_JOIN操作。从此代码创建的笛卡尔积将生成一个结果集,其总行数等于sys.tables中的行数,sys.objects中的行数乘以sys.sysusers中的行数。
当CROSS JOIN像INNER JOIN一样执行时
在前面的章节中,我提到当你使用CROSS JOIN操作符时,它将产生一个笛卡尔积。这一直是不正确的。当您使用限制CROSS JOIN操作中涉及的表的连接的WHERE子句时,SQL Server不会创建笛卡尔积。相反,它的功能就像一个正常的JOIN操作。为了演示这种行为,请查看清单5中的代码。
SELECT * FROM产品P CROSS JOIN SalesItem S
WHERE P.ID = S.ProductID;
SELECT * FROM产品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
左外连接
SalesItem S2
打开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操作创建的笛卡尔积结合起来。我基于ProductID和SalesDate列执行了此连接。通过使用LEFT OUTER JOIN,我的笛卡尔积中的每一行都会被返回,如果ProductID和SalesDate有一个匹配的SalesDate记录,则Qty和TotalSalesAmt值将与相应的行关联。这个查询所做的最后一件事是使用GROUP BY子句来总结基于SalesDate和ProductName的Qty和TotalSalesAmount。
性能考虑
生产笛卡尔产品的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个月的销售数据的表。如果我想生成一份报告,显示一个月内没有任何销售的客户,那么确定一个月中的天数可以彻底改变我的查询的性能。为了证明这一点,我首先为两个月的时间为1000位客户创建一组销售记录。我将使用清单7中的代码执行此操作。
CREATE TABLE Cust(Id int,CustName varchar(20));
CREATE TABLE Sales(Id int identity
,CustID int
,SaleDate日期
,SalesAmt钱);
SET NOCOUNT ON;
DECLARE @I int = 0;
DECLARE @Date date;
尽管@我<1000
开始
SET @I = @I + 1;
SET @Date = DATEADD(mm,-2,‘2014-11-01‘);
插入到Cust
VALUES(@I,
‘Customer#‘+ right(cast(@ I + 100000 as varchar(6)),5));
WHILE @Date <‘2014-11-01‘
开始
IF @ I%7> 0
插入销售(CustID,SaleDate,SalesAmt)
VALUES(@I,@Date,10.00);
SET @Date = DATEADD(DD,1,@Date);
结束
结束
清单7:TSQL为性能测试创建示例数据
清单7中的代码为1,000个不同的客户创建了2个月的数据。此代码不会为每个第7个客户添加销售数据。此代码生成1,000个Cust表记录和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
(
从销售中选择SaleDate
)AS S1
左外连接
销售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
清单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
左外连接
销售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
清单9:CROSS JOIN针对不同的销售日期列表
在清单8中,CROSS JOIN操作符将1,000个Cust记录与52,338个销售记录结合在一起,生成一个52,338,000行记录集,然后用它来确定cu
结论
CROSS JOIN操作符在两个记录集之间生成笛卡尔积。此操作符在帮助识别一个表中没有另一个表中的匹配记录的项目时非常有用。应小心使用CROSS JOIN操作符所使用的记录集的大小。通过确保CROSS JOIN的结果集尽可能小,您将确保您的代码尽可能快地运行。
问题和答案
在本节中,您可以通过回答以下问题来查看使用CROSS JOIN操作符的理解程度。
问题1:
CROSS JOIN运算符通过基于ON子句中指定的列匹配两个记录集来创建一个结果集。 (对或错)?
真正
假
问题2:
当表A和B包含重复行时,可以使用哪个公式来标识将从两个表A和B之间的不受约束的CROSS JOIN返回的行数?
表中的行数A乘以表B中的行数
表A中的行数A乘以表B中的唯一行数
表A中的唯一行数A乘以表B中的行数
表B中的唯一行数A乘以表B中的唯一行数
问题3:
哪种方法最有可能减小CROSS JOIN操作产生的笛卡尔积?
确保两组被连接的行数尽可能多
确保连接的两组尽可能少
确保CROSS JOIN操作左侧的行数尽可能少
确保CROSS JOIN操作右侧的行数尽可能少
回答:
问题1:
正确答案是b。 CROSS JOIN运算符不使用ON子句执行CROSS JOIN操作。它将一个表中的每一行连接到另一个表中的每一行。 CROSS JOIN在连接两组时创建一个笛卡尔积。
问题2:
正确的答案是。 b,c和d是不正确的,因为如果表A或B中有重复的行,则在为CROSS JOIN操作创建笛卡尔积时,每个重复行都是连接。
问题3:
正确答案是b。通过减少在CROSS JOIN操作中涉及的两个集合的大小,使由CROSS JOI操作创建的最终集合的大小最小化。 c和d也有助于减小由CROSS JOIN操作创建的最终集合的大小,但并不像确保CROSS JOIN操作中涉及的两个集合具有尽可能最少的行那样最佳。
本文是高级T-SQL阶梯的一部分
注册我们的RSS饲料,并获得通知,只要我们在楼梯上发布一个新的水平!
先进的T-SQL 1级楼梯:高级T-SQL使用交叉连接的介绍
作者:Gregory Larsen,2016/02/19(第一版:2014年12月17日)
该系列
本文是“Stairway Series:Stairway to Advanced T-SQL”的一部分
这个阶梯将包含一系列文章,这些文章将在前面两个T-SQL阶梯,T-SQL DML和T-SQL超越基础知识的T-SQL基础上进行扩展。这个楼梯应该帮助读者准备通过Microsoft认证考试70-461:查询Microsoft SQL Server 2012。
这是新阶梯系列中的第一篇文章,将探讨Transact SQL(TSQL)的更多高级功能。这个楼梯将包含一系列将在前两个TSQL楼梯上学习到的TSQL基础的文章:
T-SQL DML的阶梯
T-SQL的阶梯:超越基础
这个“高级Transact SQL”阶梯将涵盖以下TSQL主题:
使用CROSS JOIN运算符
使用APPLY运算符
了解公用表表达式(CTE)
使用Transact-SQL游标进行记录级别处理
使用PIVOT在其一侧转换数据
使用UNPIVOT将列转成行
使用排名函数排序数据
用函数管理日期和时间
了解OVER子句的变化
这个阶梯的读者应该已经很好的理解了如何从SQL Server表中查询,更新,插入和删除数据。另外,他们应该具有可用于控制其TSQL代码流的方法的工作知识,以及能够测试和操作数据。
这个楼梯应该帮助读者准备通过Microsoft认证考试70-461:查询Microsoft SQL Server 2012。
对于这个新的阶梯系列的第一部分,我将讨论CROSS JOIN操作符。
CROSS JOIN操作符介绍
CROSS JOIN操作符可用于将一个数据集中的所有记录组合到另一个数据集中的所有记录中。通过在两组记录之间使用CROSS JOIN运算符,您可以创建所谓的笛卡尔积。
以下是使用CROSS JOIN运算符连接两个表A和B的简单示例:
SELECT * FROM CROSS JOIN B
请注意,使用CROSS JOIN运算符时,没有连接两个表的连接子句,就像在两个表之间执行INNER和OUTER JOIN操作时所使用的那样。
你需要知道使用CROSS JOIN可以产生一个大的记录集。为了探索这种行为,我们来看看CROSS JOIN操作产生的集合有多大的两个不同的例子。对于第一个示例,假设您交叉连接两个表,其中表A有10行,而表B有3行。 CROSS JOIN的resulti集合将是10或3或30行的10倍。对于第二个例子,假设表A有1000万行,而表B有300万行。表A和B之间的CROSS JOIN结果集中有多少行?那将是高达300,000,000,000,000行。这是很多行,它将花费大量的时间和大量的资源来创建结果集。因此,在大型记录集上使用CROSS JOIN操作符时需要小心。
让我们仔细研究一下使用CROSS JOIN操作符的例子。
使用CROSS JOIN的基本示例
对于前两个例子,我们将加入两个样本表。清单1中的代码将用于创建这两个示例表。确保在用户数据库中运行这些脚本,而不是在主服务器上运行。
CREATE TABLE产品(ID int,
ProductName varchar(100),
成本钱);
CREATE TABLE SalesItem(ID int,
SalesDate datetime,
ProductID int,
数量int,
TotalSalesAmt钱);
插入产品
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);
清单1:CROSS JOIN的示例表
对于第一个CROSS JOIN示例,我将运行清单2中的代码。
选择*从
产品CROSS JOIN SalesItem;
清单2:简单的CROSS JOIN示例
当我在SQL Server Management Studio窗口中运行清单2中的代码时,使用会话设置来输出文本结果,我在Report 1中得到了输出结果:
编号产品名称成本编号销售日期产品编号数量总计销售额
--- --------------------- -------- ---- -------------- --------- --------- ---- ---------------
1小工具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小工具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行。
为什么创建笛卡尔产品可能有用的一个原因是生成测试数据。假设我想在Product和SalesItem表中使用日期来生成许多不同的产品。我可以使用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成本
FROM Product CROSS JOIN SalesItem;
清单3:简单的CROSS JOIN示例
当我运行清单3中的代码时,我得到了Report 2中的输出。
ID产品名称成本
----- --------------------------------------------- -------------- ---------------------
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
报告2:运行清单3时的结果
正如您通过查看清单3中的代码所看到的,我生成了一些包含与我的Product表中的数据类似的数据的行。通过使用ROW_NUMBER函数,我能够在每一行上生成一个唯一的ID列。此外,我使用我的SalesItem表中的ID列来创建唯一的ProductName和Cost列值。产生的行数等于Product表中的行数乘以SalesItem表中的行数。
到目前为止,该部分中的示例仅在两个表中执行了CROSS JOIN。您可以使用CROSS JOIN运算符跨多个表执行CROSS JOIN操作。清单4中的示例在三个表中创建了一个Cartesian产品。
SELECT * FROM sys.tables
CROSS JOIN sys.objects
CROSS JOIN sys.sysusers;
清单4:使用CROSS JOIN运算符创建三个表的笛卡尔积
运行清单4的输出有两个不同的CROSS_JOIN操作。从此代码创建的笛卡尔积将生成一个结果集,其总行数等于sys.tables中的行数,sys.objects中的行数乘以sys.sysusers中的行数。
当CROSS JOIN像INNER JOIN一样执行时
在前面的章节中,我提到当你使用CROSS JOIN操作符时,它将产生一个笛卡尔积。这一直是不正确的。当您使用限制CROSS JOIN操作中涉及的表的连接的WHERE子句时,SQL Server不会创建笛卡尔积。相反,它的功能就像一个正常的JOIN操作。为了演示这种行为,请查看清单5中的代码。
SELECT * FROM产品P CROSS JOIN SalesItem S
WHERE P.ID = S.ProductID;
SELECT * FROM产品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
左外连接
SalesItem S2
打开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操作创建的笛卡尔积结合起来。我基于ProductID和SalesDate列执行了此连接。通过使用LEFT OUTER JOIN,我的笛卡尔积中的每一行都会被返回,如果ProductID和SalesDate有一个匹配的SalesDate记录,则Qty和TotalSalesAmt值将与相应的行关联。这个查询所做的最后一件事是使用GROUP BY子句来总结基于SalesDate和ProductName的Qty和TotalSalesAmount。
性能考虑
生产笛卡尔产品的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个月的销售数据的表。如果我想生成一份报告,显示一个月内没有任何销售的客户,那么确定一个月中的天数可以彻底改变我的查询的性能。为了证明这一点,我首先为两个月的时间为1000位客户创建一组销售记录。我将使用清单7中的代码执行此操作。
CREATE TABLE Cust(Id int,CustName varchar(20));
CREATE TABLE Sales(Id int identity
,CustID int
,SaleDate日期
,SalesAmt钱);
SET NOCOUNT ON;
DECLARE @I int = 0;
DECLARE @Date date;
尽管@我<1000
开始
SET @I = @I + 1;
SET @Date = DATEADD(mm,-2,‘2014-11-01‘);
插入到Cust
VALUES(@I,
‘Customer#‘+ right(cast(@ I + 100000 as varchar(6)),5));
WHILE @Date <‘2014-11-01‘
开始
IF @ I%7> 0
插入销售(CustID,SaleDate,SalesAmt)
VALUES(@I,@Date,10.00);
SET @Date = DATEADD(DD,1,@Date);
结束
结束
清单7:TSQL为性能测试创建示例数据
清单7中的代码为1,000个不同的客户创建了2个月的数据。此代码不会为每个第7个客户添加销售数据。此代码生成1,000个Cust表记录和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
(
从销售中选择SaleDate
)AS S1
左外连接
销售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
清单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
左外连接
销售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
清单9:CROSS JOIN针对不同的销售日期列表
在清单8中,CROSS JOIN操作符将1,000个Cust记录与52,338个销售记录结合在一起,生成一个52,338,000行记录集,然后用它来确定cu
结论
CROSS JOIN操作符在两个记录集之间生成笛卡尔积。此操作符在帮助识别一个表中没有另一个表中的匹配记录的项目时非常有用。应小心使用CROSS JOIN操作符所使用的记录集的大小。通过确保CROSS JOIN的结果集尽可能小,您将确保您的代码尽可能快地运行。
问题和答案
在本节中,您可以通过回答以下问题来查看使用CROSS JOIN操作符的理解程度。
问题1:
CROSS JOIN运算符通过基于ON子句中指定的列匹配两个记录集来创建一个结果集。 (对或错)?
真正
假
问题2:
当表A和B包含重复行时,可以使用哪个公式来标识将从两个表A和B之间的不受约束的CROSS JOIN返回的行数?
表中的行数A乘以表B中的行数
表A中的行数A乘以表B中的唯一行数
表A中的唯一行数A乘以表B中的行数
表B中的唯一行数A乘以表B中的唯一行数
问题3:
哪种方法最有可能减小CROSS JOIN操作产生的笛卡尔积?
确保两组被连接的行数尽可能多
确保连接的两组尽可能少
确保CROSS JOIN操作左侧的行数尽可能少
确保CROSS JOIN操作右侧的行数尽可能少
回答:
问题1:
正确答案是b。 CROSS JOIN运算符不使用ON子句执行CROSS JOIN操作。它将一个表中的每一行连接到另一个表中的每一行。 CROSS JOIN在连接两组时创建一个笛卡尔积。
问题2:
正确的答案是。 b,c和d是不正确的,因为如果表A或B中有重复的行,则在为CROSS JOIN操作创建笛卡尔积时,每个重复行都是连接。
问题3:
正确答案是b。通过减少在CROSS JOIN操作中涉及的两个集合的大小,使由CROSS JOI操作创建的最终集合的大小最小化。 c和d也有助于减小由CROSS JOIN操作创建的最终集合的大小,但并不像确保CROSS JOIN操作中涉及的两个集合具有尽可能最少的行那样最佳。
本文是高级T-SQL阶梯的一部分
注册我们的RSS饲料,并获得通知,只要我们在楼梯上发布一个新的水平!
先进的T-SQL 1级楼梯:高级T-SQL使用交叉连接的介绍
作者:Gregory Larsen,2016/02/19(第一版:2014年12月17日)
该系列
本文是“Stairway Series:Stairway to Advanced T-SQL”的一部分
这个阶梯将包含一系列文章,这些文章将在前面两个T-SQL阶梯,T-SQL DML和T-SQL超越基础知识的T-SQL基础上进行扩展。这个楼梯应该帮助读者准备通过Microsoft认证考试70-461:查询Microsoft SQL Server 2012。
这是新阶梯系列中的第一篇文章,将探讨Transact SQL(TSQL)的更多高级功能。这个楼梯将包含一系列将在前两个TSQL楼梯上学习到的TSQL基础的文章:
T-SQL DML的阶梯
T-SQL的阶梯:超越基础
这个“高级Transact SQL”阶梯将涵盖以下TSQL主题:
使用CROSS JOIN运算符
使用APPLY运算符
了解公用表表达式(CTE)
使用Transact-SQL游标进行记录级别处理
使用PIVOT在其一侧转换数据
使用UNPIVOT将列转成行
使用排名函数排序数据
用函数管理日期和时间
了解OVER子句的变化
这个阶梯的读者应该已经很好的理解了如何从SQL Server表中查询,更新,插入和删除数据。另外,他们应该具有可用于控制其TSQL代码流的方法的工作知识,以及能够测试和操作数据。
这个楼梯应该帮助读者准备通过Microsoft认证考试70-461:查询Microsoft SQL Server 2012。
对于这个新的阶梯系列的第一部分,我将讨论CROSS JOIN操作符。
CROSS JOIN操作符介绍
CROSS JOIN操作符可用于将一个数据集中的所有记录组合到另一个数据集中的所有记录中。通过在两组记录之间使用CROSS JOIN运算符,您可以创建所谓的笛卡尔积。
以下是使用CROSS JOIN运算符连接两个表A和B的简单示例:
SELECT * FROM CROSS JOIN B
请注意,使用CROSS JOIN运算符时,没有连接两个表的连接子句,就像在两个表之间执行INNER和OUTER JOIN操作时所使用的那样。
你需要知道使用CROSS JOIN可以产生一个大的记录集。为了探索这种行为,我们来看看CROSS JOIN操作产生的集合有多大的两个不同的例子。对于第一个示例,假设您交叉连接两个表,其中表A有10行,而表B有3行。 CROSS JOIN的resulti集合将是10或3或30行的10倍。对于第二个例子,假设表A有1000万行,而表B有300万行。表A和B之间的CROSS JOIN结果集中有多少行?那将是高达300,000,000,000,000行。这是很多行,它将花费大量的时间和大量的资源来创建结果集。因此,在大型记录集上使用CROSS JOIN操作符时需要小心。
让我们仔细研究一下使用CROSS JOIN操作符的例子。
使用CROSS JOIN的基本示例
对于前两个例子,我们将加入两个样本表。清单1中的代码将用于创建这两个示例表。确保在用户数据库中运行这些脚本,而不是在主服务器上运行。
CREATE TABLE产品(ID int,
ProductName varchar(100),
成本钱);
CREATE TABLE SalesItem(ID int,
SalesDate datetime,
ProductID int,
数量int,
TotalSalesAmt钱);
插入产品
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);
清单1:CROSS JOIN的示例表
对于第一个CROSS JOIN示例,我将运行清单2中的代码。
选择*从
产品CROSS JOIN SalesItem;
清单2:简单的CROSS JOIN示例
当我在SQL Server Management Studio窗口中运行清单2中的代码时,使用会话设置来输出文本结果,我在Report 1中得到了输出结果:
编号产品名称成本编号销售日期产品编号数量总计销售额
--- --------------------- -------- ---- -------------- --------- --------- ---- ---------------
1小工具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小工具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行。
为什么创建笛卡尔产品可能有用的一个原因是生成测试数据。假设我想在Product和SalesItem表中使用日期来生成许多不同的产品。我可以使用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成本
FROM Product CROSS JOIN SalesItem;
清单3:简单的CROSS JOIN示例
当我运行清单3中的代码时,我得到了Report 2中的输出。
ID产品名称成本
----- --------------------------------------------- -------------- ---------------------
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
报告2:运行清单3时的结果
正如您通过查看清单3中的代码所看到的,我生成了一些包含与我的Product表中的数据类似的数据的行。通过使用ROW_NUMBER函数,我能够在每一行上生成一个唯一的ID列。此外,我使用我的SalesItem表中的ID列来创建唯一的ProductName和Cost列值。产生的行数等于Product表中的行数乘以SalesItem表中的行数。
到目前为止,该部分中的示例仅在两个表中执行了CROSS JOIN。您可以使用CROSS JOIN运算符跨多个表执行CROSS JOIN操作。清单4中的示例在三个表中创建了一个Cartesian产品。
SELECT * FROM sys.tables
CROSS JOIN sys.objects
CROSS JOIN sys.sysusers;
清单4:使用CROSS JOIN运算符创建三个表的笛卡尔积
运行清单4的输出有两个不同的CROSS_JOIN操作。从此代码创建的笛卡尔积将生成一个结果集,其总行数等于sys.tables中的行数,sys.objects中的行数乘以sys.sysusers中的行数。
当CROSS JOIN像INNER JOIN一样执行时
在前面的章节中,我提到当你使用CROSS JOIN操作符时,它将产生一个笛卡尔积。这一直是不正确的。当您使用限制CROSS JOIN操作中涉及的表的连接的WHERE子句时,SQL Server不会创建笛卡尔积。相反,它的功能就像一个正常的JOIN操作。为了演示这种行为,请查看清单5中的代码。
SELECT * FROM产品P CROSS JOIN SalesItem S
WHERE P.ID = S.ProductID;
SELECT * FROM产品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
左外连接
SalesItem S2
打开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操作创建的笛卡尔积结合起来。我基于ProductID和SalesDate列执行了此连接。通过使用LEFT OUTER JOIN,我的笛卡尔积中的每一行都会被返回,如果ProductID和SalesDate有一个匹配的SalesDate记录,则Qty和TotalSalesAmt值将与相应的行关联。这个查询所做的最后一件事是使用GROUP BY子句来总结基于SalesDate和ProductName的Qty和TotalSalesAmount。
性能考虑
生产笛卡尔产品的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个月的销售数据的表。如果我想生成一份报告,显示一个月内没有任何销售的客户,那么确定一个月中的天数可以彻底改变我的查询的性能。为了证明这一点,我首先为两个月的时间为1000位客户创建一组销售记录。我将使用清单7中的代码执行此操作。
CREATE TABLE Cust(Id int,CustName varchar(20));
CREATE TABLE Sales(Id int identity
,CustID int
,SaleDate日期
,SalesAmt钱);
SET NOCOUNT ON;
DECLARE @I int = 0;
DECLARE @Date date;
尽管@我<1000
开始
SET @I = @I + 1;
SET @Date = DATEADD(mm,-2,‘2014-11-01‘);
插入到Cust
VALUES(@I,
‘Customer#‘+ right(cast(@ I + 100000 as varchar(6)),5));
WHILE @Date <‘2014-11-01‘
开始
IF @ I%7> 0
插入销售(CustID,SaleDate,SalesAmt)
VALUES(@I,@Date,10.00);
SET @Date = DATEADD(DD,1,@Date);
结束
结束
清单7:TSQL为性能测试创建示例数据
清单7中的代码为1,000个不同的客户创建了2个月的数据。此代码不会为每个第7个客户添加销售数据。此代码生成1,000个Cust表记录和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
(
从销售中选择SaleDate
)AS S1
左外连接
销售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
清单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
左外连接
销售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
清单9:CROSS JOIN针对不同的销售日期列表
在清单8中,CROSS JOIN操作符将1,000个Cust记录与52,338个销售记录结合在一起,生成一个52,338,000行记录集,然后用它来确定cu
结论
CROSS JOIN操作符在两个记录集之间生成笛卡尔积。此操作符在帮助识别一个表中没有另一个表中的匹配记录的项目时非常有用。应小心使用CROSS JOIN操作符所使用的记录集的大小。通过确保CROSS JOIN的结果集尽可能小,您将确保您的代码尽可能快地运行。
问题和答案
在本节中,您可以通过回答以下问题来查看使用CROSS JOIN操作符的理解程度。
问题1:
CROSS JOIN运算符通过基于ON子句中指定的列匹配两个记录集来创建一个结果集。 (对或错)?
真正
假
问题2:
当表A和B包含重复行时,可以使用哪个公式来标识将从两个表A和B之间的不受约束的CROSS JOIN返回的行数?
表中的行数A乘以表B中的行数
表A中的行数A乘以表B中的唯一行数
表A中的唯一行数A乘以表B中的行数
表B中的唯一行数A乘以表B中的唯一行数
问题3:
哪种方法最有可能减小CROSS JOIN操作产生的笛卡尔积?
确保两组被连接的行数尽可能多
确保连接的两组尽可能少
确保CROSS JOIN操作左侧的行数尽可能少
确保CROSS JOIN操作右侧的行数尽可能少
回答:
问题1:
正确答案是b。 CROSS JOIN运算符不使用ON子句执行CROSS JOIN操作。它将一个表中的每一行连接到另一个表中的每一行。 CROSS JOIN在连接两组时创建一个笛卡尔积。
问题2:
正确的答案是。 b,c和d是不正确的,因为如果表A或B中有重复的行,则在为CROSS JOIN操作创建笛卡尔积时,每个重复行都是连接。
问题3:
正确答案是b。通过减少在CROSS JOIN操作中涉及的两个集合的大小,使由CROSS JOI操作创建的最终集合的大小最小化。 c和d也有助于减小由CROSS JOIN操作创建的最终集合的大小,但并不像确保CROSS JOIN操作中涉及的两个集合具有尽可能最少的行那样最佳。
本文是高级T-SQL阶梯的一部分
注册我们的RSS饲料,并获得通知,只要我们在楼梯上发布一个新的水平!
先进的T-SQL 1级楼梯:高级T-SQL使用交叉连接的介绍
原文:http://www.cnblogs.com/dzxy17/p/7820305.html