通过使用LEFT OUTER JOIN,对笛卡儿积中的每一行返回符合ProductID和SalesDate的SalesDate、Qty和TotalSalesAmt值。最后一步就是使用GROUP BY 子句汇总基于SalesDate和ProductName的Qty和TotalSalesAmount值。
性能考虑
CROSS JOIN操作由于可能产生笛卡儿积会存在一定的性能风险。因为SQL 引擎需要把两表的每一行都一一匹配并产生新数据,这个结果集可能非常巨大。如果一个有100万行的表与一个有10万行的表CROSS JOIN,结果集就是1000000*100000=100000000000 (1万亿!)。SQL Server需要花费大量的时间和资源去创建这个结果集。
但是CROSS JOIN又可以用于分析两个结果集的可能结果,比如每个月所有客户的所有销售情况,即使某些客户在某些月没有销售记录,也可以查询出对应的情况。当使用CROSS JOIN操作时,尽可能最小化CROSS JOIN的源数据和结果集,以便使性能可控。假设有一个表存放了最近2个月的销售记录,如果我需要生成一个关于哪些客户在一个月内完全没购买记录的报表时,就可以用CROSS JOIN。下面来演示一下:
首先创建两个表,有1000个客户的,存储了2个月的销售信息。
CREATE TABLECust (Id int, CustName varchar(20));
CREATE TABLESales (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);
END
END
代码中对每第七个客户添加销售记录。为了演示CROSSJOIN基于结果集导致的体积问题,分别执行下面两个代码:
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.CustName
HAVING ISNULL(SUM(S2.SalesAmt),0) = 0
ORDER BY CONVERT(CHAR(6),S1.SaleDate,112),C.CustName
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 FROMSales
) 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
在第一个查询中,1000个客户和52338个销售记录产生了52338000行结果集。在第二个查询中,仅对具有唯一SalesData值的数据进行笛卡儿积,此处仅产生了61行唯一的SalesData数据,所以CROSS JOIN之后的的结果集只有61000行。通过控制CROSS JOIN的输入规模,第二个查询只需要1秒,而第一个查询在本机需要19秒。可见输入集的规模对性能有很明显的影响。检查两者的执行计划也可以看到明显的不同。
所以在编写CROSS JOIN时,需要考虑输入集的规模,从而避免性能问题。
?
结论:
CROSS JOIN操作会在两个记录集中进行笛卡儿积操作,这个操作在检查“存在于一个表但不存在于另外一个表”时非常有用。但是需要非常小心用于CROSS JOIN的输入集的规模。把CROSS JOIN的输入集尽可能控制在必要的规模,以便保证运行效率。
-------------------------------------------------------------------个人感悟----------------------------------------------------------------------------
按照惯例,总要说些自己的看法。毕竟是以原创发布,不能简单地翻译。
首先,本文主体还是翻译,但是不是全译。读者可以自行查看原文。
其次,在这么多年的工作中,我用CROSS JOIN的确很少,因为我知道它会带来的问题。一直以来,我用这个功能的唯一目的是用来产生一个大的结果集,主要拿来测试。比如我要测试1亿行数据的某些写法、索引性能时,会用一个10000行的表互相CROSS JOIN,一亿行数据就出来了,然后随心所欲。
通常来说,我不建议在正式环境中使用CROSS JOIN,因为大部分情况下这是不合理的,但是在工作过程中,的确见到一些系统,为了产生某些结果(如报表),需要关联两个本来无关联关系的表,这样几乎只能用CROSS JOIN。当然,如果有可能,尽量使两表有关联,或者转用数据仓库的一些技术。
最后,我们应该透过现象看本质,文中提到有ON和没有ON的CROSS JOIN之间的差异,我以前见过论坛有人说过,INNER JOIN也是先CROSS JOIN 再筛选,我当时就觉得不可能,但是没有具体证据不好说什么。关系型数据库出现了接近40年,今时今日的RDBMS已经没有太多的差异,更多的是修修补补和提升一点点的性能而已。很多时候我们不能想当然,而应该用实际操作实践一下自己的想法,比如过去如果我会用前面提到的例子实践一下的话,当时我就可以告诉那个人,INNER JOIN并不是简单地先CROSS JOIN再筛选,不然太反人类了。
读者是否会想想为什么会有这篇文章?其实CROSS JOIN用得较少,并且也比较危险,在真正工作中使用得其实还是很少的。可是偏偏在微软认证中出现了(大家先别黑别人,多想想为什么,CROSS JOIN并不仅仅存在于SQL Server)。正如前面说过,除了产生测试数据之外,很多情况下(论坛回帖过程中也出现过),有些业务需求的确要用CROSS JOIN来生成,虽然这些情况大部分是可以通过改变设计来实现,不过现实情况就是很少系统有足