When should I use cross apply over inner join?
使用交叉应用的主要目的是什么?
我(隐约地,通过互联网上的帖子)读到,如果要分区的话,在选择大型数据集时,
我也知道,
在大多数
有人能给我一个很好的例子,说明
编辑:
下面是一个简单的例子,其中执行计划完全相同。(给我看一个不同的地方和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | CREATE TABLE Company ( companyId INT IDENTITY(1,1) , companyName VARCHAR(100) , zipcode VARCHAR(10) , CONSTRAINT PK_Company PRIMARY KEY (companyId) ) GO CREATE TABLE Person ( personId INT IDENTITY(1,1) , personName VARCHAR(100) , companyId INT , CONSTRAINT FK_Person_CompanyId FOREIGN KEY (companyId) REFERENCES dbo.Company(companyId) , CONSTRAINT PK_Person PRIMARY KEY (personId) ) GO INSERT Company SELECT 'ABC Company', '19808' UNION SELECT 'XYZ Company', '08534' UNION SELECT '123 Company', '10016' INSERT Person SELECT 'Alan', 1 UNION SELECT 'Bobby', 1 UNION SELECT 'Chris', 1 UNION SELECT 'Xavier', 2 UNION SELECT 'Yoshi', 2 UNION SELECT 'Zambrano', 2 UNION SELECT 'Player 1', 3 UNION SELECT 'Player 2', 3 UNION SELECT 'Player 3', 3 /* using CROSS APPLY */ SELECT * FROM Person p CROSS apply ( SELECT * FROM Company c WHERE p.companyid = c.companyId ) Czip /* the equivalent query using INNER JOIN */ SELECT * FROM Person p INNER JOIN Company c ON p.companyid = c.companyId |
Can anyone give me a good example of when CROSS APPLY makes a difference in those cases where INNER JOIN will work as well?
有关详细的性能比较,请参阅我的博客中的文章:
INNER JOIN 对CROSS APPLY 的比较
本次从
1 2 3 4 5 6 7 8 9 10 | SELECT t1.*, t2o.* FROM t1 CROSS APPLY ( SELECT TOP 3 * FROM t2 WHERE t2.t1_id = t1.id ORDER BY t2.rank DESC ) t2o |
在
您可以使用
1 2 3 4 5 6 7 8 9 10 11 | WITH t2o AS ( SELECT t2.*, ROW_NUMBER() OVER (PARTITION BY t1_id ORDER BY rank) AS rn FROM t2 ) SELECT t1.*, t2o.* FROM t1 INNER JOIN t2o ON t2o.t1_id = t1.id AND t2o.rn <= 3 |
但是,它的可读性较低,而且可能效率较低。
更新:
刚刚检查过。
此查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | WITH q AS ( SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM master ), t AS ( SELECT 1 AS id UNION ALL SELECT 2 ) SELECT * FROM t JOIN q ON q.rn <= t.id |
运行了几乎13秒,而这一秒:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | WITH t AS ( SELECT 1 AS id UNION ALL SELECT 2 ) SELECT * FROM t CROSS APPLY ( SELECT TOP (t.id) m.* FROM master m ORDER BY id ) q |
瞬间。
示例(语法错误):
1 2 3 | SELECT F.* FROM sys.objects O INNER JOIN dbo.myTableFun(O.name) F ON F.schema_id= O.schema_id |
这是一个语法错误,因为当与
然而:
1 2 3 | SELECT F.* FROM sys.objects O CROSS apply ( SELECT * FROM dbo.myTableFun(O.name) ) F WHERE F.schema_id= O.schema_id |
这是合法的。
编辑:或者,更短的语法:(由Erike编写)
1 2 3 | SELECT F.* FROM sys.objects O CROSS apply dbo.myTableFun(O.name) F WHERE F.schema_id= O.schema_id |
编辑:
注:Informix 12.10 XC2+有横向派生表,PostgreSQL(9.3+)有横向子查询,可以使用类似的效果。
假设你有两张桌子。
主表
1 2 3 4 5 6 7 | x------x--------------------x | Id | Name | x------x--------------------x | 1 | A | | 2 | B | | 3 | C | x------x--------------------x |
细节表
1 2 3 4 5 6 7 8 9 | x------x--------------------x-------x | Id | PERIOD | QTY | x------x--------------------x-------x | 1 | 2014-01-13 | 10 | | 1 | 2014-01-11 | 15 | | 1 | 2014-01-12 | 20 | | 2 | 2014-01-06 | 30 | | 2 | 2014-01-08 | 40 | x------x--------------------x-------x |
在很多情况下,我们需要用
1。根据
考虑是否需要从
1 2 3 4 5 6 7 8 9 | SELECT M.ID,M.NAME,D.PERIOD,D.QTY FROM MASTER M INNER JOIN ( SELECT TOP 2 ID, PERIOD,QTY FROM DETAILS D ORDER BY CAST(PERIOD AS DATE)DESC )D ON M.ID=D.ID |
- SQL小提琴
上述查询生成以下结果。
1 2 3 4 5 6 | x------x---------x--------------x-------x | Id | Name | PERIOD | QTY | x------x---------x--------------x-------x | 1 | A | 2014-01-13 | 10 | | 1 | A | 2014-01-12 | 20 | x------x---------x--------------x-------x |
看,它用最后两个日期的
1 2 3 4 5 6 7 8 9 | SELECT M.ID,M.NAME,D.PERIOD,D.QTY FROM MASTER M CROSS APPLY ( SELECT TOP 2 ID, PERIOD,QTY FROM DETAILS D WHERE M.ID=D.ID ORDER BY CAST(PERIOD AS DATE)DESC )D |
- SQL小提琴
形成以下结果。
1 2 3 4 5 6 7 8 | x------x---------x--------------x-------x | Id | Name | PERIOD | QTY | x------x---------x--------------x-------x | 1 | A | 2014-01-13 | 10 | | 1 | A | 2014-01-12 | 20 | | 2 | B | 2014-01-08 | 40 | | 2 | B | 2014-01-06 | 30 | x------x---------x--------------x-------x |
这就是它的工作原理。
2。当我们需要使用函数的
当需要从
1 2 3 | SELECT M.ID,M.NAME,C.PERIOD,C.QTY FROM MASTER M CROSS APPLY dbo.FnGetQty(M.ID) C |
这里是函数
1 2 3 4 5 6 7 8 9 10 11 12 | CREATE FUNCTION FnGetQty ( @Id INT ) RETURNS TABLE AS RETURN ( SELECT ID,PERIOD,QTY FROM DETAILS WHERE ID=@Id ) |
- SQL小提琴
结果如下
1 2 3 4 5 6 7 8 9 | x------x---------x--------------x-------x | Id | Name | PERIOD | QTY | x------x---------x--------------x-------x | 1 | A | 2014-01-13 | 10 | | 1 | A | 2014-01-11 | 15 | | 1 | A | 2014-01-12 | 20 | | 2 | B | 2014-01-06 | 30 | | 2 | B | 2014-01-08 | 40 | x------x---------x--------------x-------x |
交叉应用的附加优势
假设您有下表(名为
1 2 3 4 5 6 7 8 | x------x-------------x--------------x | Id | FROMDATE | TODATE | x------x-------------x--------------x | 1 | 2014-01-11 | 2014-01-13 | | 1 | 2014-02-23 | 2014-02-27 | | 2 | 2014-05-06 | 2014-05-30 | | 3 | NULL | NULL | x------x-------------x--------------x |
查询如下。
1 2 3 4 | SELECT DISTINCT ID,DATES FROM MYTABLE CROSS APPLY(VALUES (FROMDATE),(TODATE)) COLUMNNAMES(DATES) |
- SQL小提琴
这给你带来了结果
1 2 3 4 5 6 7 8 9 10 11 | x------x-------------x | Id | DATES | x------x-------------x | 1 | 2014-01-11 | | 1 | 2014-01-13 | | 1 | 2014-02-23 | | 1 | 2014-02-27 | | 2 | 2014-05-06 | | 2 | 2014-05-30 | | 3 | NULL | x------x-------------x |
在我看来,在复杂/嵌套查询中使用计算字段时,交叉应用可以填补一定的空白,并使它们更简单、更可读。
简单示例:您有一个DOB,并且希望显示多个与年龄相关的字段,这些字段还将依赖于其他数据源(如就业),如年龄、年龄组、年龄感应、最低退休日期等,以在最终用户应用程序(例如Excel数据透视表)中使用。
选项有限,很少优雅:
join子查询不能基于父查询中的数据在数据集中引入新值(它必须独立存在)。
UDF很整洁,但速度很慢,因为它们往往会阻止并行操作。作为一个独立的实体可能是一件好事(代码较少)或坏事(代码在哪里)。
接线表。有时它们可以工作,但很快您就可以将子查询与大量的联合连接起来。大乱。
创建另一个单用途视图,假设您的计算不需要通过主查询中途获得数据。
中间表。对。。。这通常是可行的,而且通常是一个很好的选择,因为它们可以被索引和快速,但是性能也会下降,因为更新语句不是并行的,并且不允许级联公式(重用结果)更新同一语句中的多个字段。有时候你更喜欢一次就做事情。
嵌套查询。是的,在任何时候,您都可以在整个查询上加上括号,并将其用作子查询,在子查询上您可以类似地操作源数据和计算字段。但你只能在它变丑之前做这么多。非常丑陋。
重复代码。3个长(case…else…end)语句的最大值是什么?那将是可读的!
- 告诉你的客户自己计算这些该死的东西。
我错过什么了吗?可能吧,请随意评论。但是,在这种情况下,交叉应用就像是天赐之物:只需添加一个简单的
通过交叉应用引入的值可以…
- 用于创建一个或多个计算字段,而不向组合添加性能、复杂性或可读性问题。
- 和join一样,后面的几个交叉应用语句也可以引用它们自己:
CROSS APPLY (select crossTbl.someFormula + 1 as someMoreFormula) as crossTbl2 。 - 可以在后续联接条件中使用交叉应用引入的值
- 另外,还有表值函数方面
党,他们什么都做不了!
下面是一个例子,当交叉应用与性能产生巨大差异时:
使用交叉应用优化条件之间的联接
注意,除了替换内部联接之外,您还可以重用代码,如截断日期,而不必为调用标量UDF支付性能惩罚,例如:使用内联UDF计算月份的第三个星期三
交叉应用也适用于XML字段。如果希望结合其他字段选择节点值。
例如,如果有一个表包含一些XML
1
2
3
4
5
6
7
8 <root>
<subnode1>
<some_node VALUE="1" />
<some_node VALUE="2" />
<some_node VALUE="3" />
<some_node VALUE="4" />
</subnode1>
</root>
使用查询
1 2 3 4 5 6 7 8 9 | SELECT id AS [xt_id] ,xmlfield.value('(/root/@attribute)[1]', 'varchar(50)') root_attribute_value ,node_attribute_value = [some_node].value('@value', 'int') ,lt.lt_name FROM dbo.table_with_xml xt CROSS APPLY xmlfield.nodes('/root/subnode1/some_node') AS g ([some_node]) LEFT OUTER JOIN dbo.lookup_table lt ON [some_node].value('@value', 'int') = lt.lt_id |
将返回结果
1 2 3 4 | xt_id root_attribute_value node_attribute_value lt_name ---------------------------------------------------------------------- 1 test1 1 Benefits 1 test1 4 FINRPTCOMPANY |
交叉应用可用于替换需要子查询列的子查询
子查询
1 2 | SELECT * FROM person p WHERE p.companyId IN(SELECT c.companyId FROM company c WHERE c.companyname LIKE '%yyy%') |
在这里,我将无法选择公司表的列因此,使用交叉应用
1 2 3 4 5 6 7 | SELECT P.*,T.CompanyName FROM Person p CROSS apply ( SELECT * FROM Company C WHERE p.companyid = c.companyId AND c.CompanyName LIKE '%yyy%' ) T |
我想应该是可读性;)
交叉应用对于阅读的人来说是有点独特的,他们会告诉他们正在使用一个UDF,它将应用于左侧表格中的每一行。
当然,还有其他的限制,即交叉应用比其他朋友在上面发布的连接更好地使用。
技术上已经很好地回答了这个问题,但是让我举一个具体的例子说明它是如何非常有用的:
假设您有两张桌子,客户和订单。客户有很多订单。
我想创建一个视图,向我提供有关客户的详细信息,以及他们最近的订单。使用just join,这将需要一些自连接和聚合,但这并不美观。但是使用交叉应用,它非常容易:
1 2 3 4 5 6 7 8 | SELECT * FROM Customer CROSS APPLY ( SELECT TOP 1 * FROM ORDER WHERE ORDER.CustomerId = Customer.CustomerId ORDER BY OrderDate DESC ) T |
这里有一篇文章解释了这一切,以及它们在连接上的性能差异和用法。
SQL Server交叉应用和外部应用于联接
正如本文所建议的,对于正常的连接操作(内部和交叉),它们之间没有性能差异。
当您必须执行如下查询时,会出现使用差异:
1 2 3 4 5 6 7 8 9 10 11 | CREATE FUNCTION dbo.fn_GetAllEmployeeOfADepartment(@DeptID AS INT) RETURNS TABLE AS RETURN ( SELECT * FROM Employee E WHERE E.DepartmentID = @DeptID ) GO SELECT * FROM Department D CROSS APPLY dbo.fn_GetAllEmployeeOfADepartment(D.DepartmentID) |
也就是说,当你必须与功能相关时。使用内部联接无法完成此操作,这将导致错误"无法绑定多部分标识符"d.DepartmentID"。在这里,在读取每一行时,值将传递给函数。听起来很酷。:)
嗯,我不确定这是否符合使用交叉应用与内部连接的理由,但这个问题是在使用交叉应用的论坛帖子中回答的,所以我不确定是否有使用内部连接的相等方法:
1 2 3 4 | CREATE PROCEDURE [dbo].[Message_FindHighestMatches] -- Declare the Topical Neighborhood @TopicalNeighborhood NCHAR(255) |
AS开始
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON CREATE TABLE #temp ( MessageID INT, Subjects NCHAR(255), SubjectsCount INT ) INSERT INTO #temp SELECT MessageID, Subjects, SubjectsCount FROM Message SELECT Top 20 MessageID, Subjects, SubjectsCount, (t.cnt * 100)/t3.inputvalues AS MatchPercentage FROM #temp CROSS apply (SELECT COUNT(*) AS cnt FROM dbo.Split(Subjects,',') AS t1 JOIN dbo.Split(@TopicalNeighborhood,',') AS t2 ON t1.value = t2.value) AS t CROSS apply (SELECT COUNT(*) AS inputValues FROM dbo.Split(@TopicalNeighborhood,',')) AS t3 ORDER BY MatchPercentage DESC DROP TABLE #temp |
结束
APPLY运算符的本质是允许FROM子句中运算符的左侧和右侧之间存在相关性。
与join相反,不允许输入之间的相关性。
说到应用运算符中的相关性,我的意思是在右边我们可以放置:
- 派生表-作为带有别名的相关子查询
- 表值函数-带有参数的概念视图,其中参数可以引用左侧
两者都可以返回多个列和行。
这也许是一个古老的问题,但我仍然喜欢交叉应用的功能,它简化了逻辑的重用,并为结果提供了一个"链接"机制。
我在下面提供了一个SQL fiddle,它展示了一个简单的示例,说明如何使用交叉应用来对数据集执行复杂的逻辑操作,而不会造成任何混乱。从这里不难推断出更复杂的计算。
http://sqlfiddle.com/!3/23862/2