我应该什么时候使用交叉申请内部加入?
使用CROSS APPLY的主要目的是什么?
我已经阅读(隐约地通过互联网上的帖子),如果您正在进行分区,那么在选择大数据集时, cross apply
可以更有效。 (寻呼想到)
我也知道CROSS APPLY
不需要UDF作为右表。
在大多数INNER JOIN
查询(一对多关系)中,我可以重写它们来使用CROSS APPLY
,但是它们总是给我等同的执行计划。
任何人都可以给我一个很好的例子,当CROSS APPLY
在INNER JOIN
也能工作的情况下INNER JOIN
吗?
编辑:
这是一个简单的例子,执行计划是完全一样的。 (给我看一个他们不同的地方, cross apply
地方是更快/更有效率)
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
任何人都可以给我一个很好的例子,当CROSS APPLY在INNER JOIN也能工作的情况下有所作为吗?
有关详细的性能比较,请参阅我的博客中的文章:
-
INNER JOIN
与CROSS APPLY
CROSS APPLY
在没有简单连接条件的情况下效果更好。
这个从t2
为t1
每个记录选择3
最后的记录:
SELECT t1.*, t2o.* FROM t1 CROSS APPLY ( SELECT TOP 3 * FROM t2 WHERE t2.t1_id = t1.id ORDER BY t2.rank DESC ) t2o
用INNER JOIN
条件不能简单地制定。
你可以使用CTE
和窗口函数做类似的事情:
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
,但是这是不太可读的,可能效率较低。
更新:
刚刚检查。
master
是一个包含id
为PRIMARY KEY
的大约20,000,000
记录的表。
这个查询:
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
运行了近30
秒,而这一个:
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
是即时的。
cross apply
有时可以让你做inner join
不能做的事情。
示例(语法错误):
select F.* from sys.objects O inner join dbo.myTableFun(O.name) F on F.schema_id= O.schema_id
这是一个语法错误 ,因为与inner join
一起使用时,表函数只能将变量或常量用作参数。 (即,表函数参数不能依赖于另一个表的列)。
然而:
select F.* from sys.objects O cross apply ( select * from dbo.myTableFun(O.name) ) F where F.schema_id= O.schema_id
这是合法的。
编辑:或者,更短的语法:(通过ErikE)
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+)具有可用于类似效果的横向子查询 。
考虑你有两个表。
主表
x------x--------------------x | Id | Name | x------x--------------------x | 1 | A | | 2 | B | | 3 | C | x------x--------------------x
细节表
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
有很多情况下,我们需要用CROSS APPLY
替换INNER JOIN
。
1.根据TOP n
结果加入两个表格
考虑是否需要从Master
Details table
选择Id
和Name
,并从Details table
为每个Id
选择最后两个日期。
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 FIDDLE
上面的查询生成以下结果。
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
看,它产生了最后两个日期的最后两个日期的结果,然后加入这些记录只在Id
的外部查询,这是错误的。 要做到这一点,我们需要使用CROSS APPLY
。
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 FIDDLE
并形成以下结果。
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
这是如何工作的。 CROSS APPLY
内部的查询可以引用外部表, INNER JOIN
不能这样做(它会引发编译错误)。 在找到最后两个日期时,加入是在CROSS APPLY
内完成的,即WHERE M.ID=D.ID
2.当我们需要INNER JOIN
功能使用功能。
当我们需要从Master
表和一个function
得到结果时, CROSS APPLY
可以用作INNER JOIN
的替换。
SELECT M.ID,M.NAME,C.PERIOD,C.QTY FROM MASTER M CROSS APPLY dbo.FnGetQty(M.ID) C
这是功能
CREATE FUNCTION FnGetQty ( @Id INT ) RETURNS TABLE AS RETURN ( SELECT ID,PERIOD,QTY FROM DETAILS WHERE ID=@Id )
- SQL FIDDLE
这产生了以下结果
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
交叉应用的附加优点
APPLY
可以用来替代UNPIVOT
。 CROSS APPLY
或OUTER APPLY
可以在这里使用,这是可以互换的。
考虑你有下面的表(名为MYTABLE
)。
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
查询如下。
SELECT DISTINCT ID,DATES FROM MYTABLE CROSS APPLY(VALUES (FROMDATE),(TODATE)) COLUMNNAMES(DATES)
- SQL FIDDLE
这给你带来了结果
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
这是CROSS APPLY与性能有很大区别的一个例子:
使用CROSS APPLY来优化在BETWEEN条件下的连接
请注意,除了替换内部联接之外,还可以重新使用代码(如截断日期),而无需为归入标量UDF支付性能损失,例如: 使用内联UDF计算月的第三个星期三
在我看来,CROSS APPLY在处理复杂/嵌套查询中的计算字段时可以填补一定的空白,并使其更简单,更具可读性。
简单的例子:你有一个DoB,你想呈现多个年龄相关的字段,也将依赖于其他数据源(如就业),如年龄,AgeGroup,AgeAtHiring,MinimumRetirementDate等用于您的最终用户的应用程序(例如Excel数据透视表)。
选项是有限的,很少优雅:
-
JOIN子查询不能基于父查询中的数据在数据集中引入新值(它必须独立)。
-
UDF是整齐的,但速度慢,因为它们往往会阻止并行操作。 作为一个单独的实体可以是一个好的(少代码)或坏的(代码在哪里)的事情。
-
连接表。 有时他们可以工作,但很快你就会加入大量UNION的子查询。 大混乱。
-
创建又一个单一目的视图,假设您的计算不需要通过主要查询中途获得的数据。
-
中介表。 是的…通常是有效的,通常是一个很好的选择,因为它们可以被索引和快速,但是由于UPDATE语句不是平行的,并且不允许级联公式(重用结果)来更新同样的说法。 有时候,你只是喜欢一口气做事。
-
嵌套查询。 是的,在任何时候,您都可以在整个查询中放置括号,并将其用作子查询,您可以在其上操作源数据和计算字段。 但是在丑陋之前,你只能这么做。 十分难看。
-
重复代码。 什么是3长(CASE … ELSE … END)陈述的最大价值? 这将是可读的!
- 告诉你的客户自己计算该死的东西。
我错过了什么? 也许,请随时发表评论。 但是,嗨,CROSS APPLY在这种情况下就像是天赐之物:你只需要添加一个简单的CROSS APPLY (select tbl.value + 1 as someFormula) as crossTbl
并且CROSS APPLY (select tbl.value + 1 as someFormula) as crossTbl
! 您的新领域现在已经准备好了,就像您的源数据中一直存在的一样。
通过CROSS APPLY引入的值可以…
- 用于创建一个或多个计算字段,而不会增加组合的性能,复杂性或可读性问题
- 像JOIN一样,几个后续的CROSS APPLY语句可以引用它们自己:
CROSS APPLY (select crossTbl.someFormula + 1 as someMoreFormula) as crossTbl2
- 您可以在随后的JOIN条件下使用CROSS APPLY引入的值
- 作为奖励,还有Table-valued功能方面
当他们做不了什么
交叉申请也适用于XML领域。 如果您希望与其他字段组合选择节点值。
例如,如果你有一个包含一些XML的表
<root> <subnode1> <some_node value="1" /> <some_node value="2" /> <some_node value="3" /> <some_node value="4" /> </subnode1> </root>
使用查询
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
会返回一个结果
xt_id root_attribute_value node_attribute_value lt_name ---------------------------------------------------------------------- 1 test1 1 Benefits 1 test1 4 FINRPTCOMPANY
我想这应该是可读性;)
对于阅读的人来说,CROSS APPLY将会有点独特,告诉他们UDF正在被使用,将被应用于左边表格的每一行。
当然,还有其他的限制,比其他朋友在上面发布的JOIN更适合使用CROSS APPLY。
那么我不知道这是否有资格作为使用交叉应用与内部联接的理由,但是这个查询在论坛帖子中使用交叉应用来回答,所以我不确定是否存在使用内部联接的均衡方法:
Create PROCEDURE [dbo].[Message_FindHighestMatches] -- Declare the Topical Neighborhood @TopicalNeighborhood nchar(255)
作为开始
-- 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
结束
在您需要子查询的列的地方,可以使用交叉应用来替换子查询
子查询
select * from person p where p.companyId in(select c.companyId from company c where c.companyname like '%yyy%')
在这里,我将无法选择公司表的列,所以使用交叉应用
select P.*,T.CompanyName from Person p cross apply ( select * from Company C where p.companyid = c.companyId and c.CompanyName like '%yyy%' ) T
这里有一篇文章解释了这一切,他们的性能差异和用法超过JOINS。
SQL Server交叉应用和外部应用联接
正如本文所建议的那样,对于正常的联接操作(INNER和CROSS),它们之间没有性能差异。
使用差异到达时,你必须做这样的查询:
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)
也就是说,当你必须与功能相关联。 这不能用INNER JOIN来完成,这会给你错误“多部分标识符”D.DepartmentID“无法绑定”。 在读取每一行时,这个值被传递给函数。 听起来很酷我。 🙂
这可能是一个老问题,但我仍然喜欢CROSS APPLY的功能,以简化逻辑的重用,并为结果提供“链接”机制。
我在下面提供了一个SQL小提琴,它展示了一个简单的例子,说明如何使用CROSS APPLY在数据集上执行复杂的逻辑操作,而不会让事情变得麻烦。 从这里推断更复杂的计算并不难。