我什么时候应该使用交叉申请内部连接?
-
16-09-2019 - |
题
使用的主要目的是什么 交叉应用?
我已经读过(模糊地通过互联网上的帖子) cross apply
如果要分区,则选择大型数据集时可以更有效。 (脑海中浮现)
我也知道 CROSS APPLY
不需要UDF作为正确的桌子。
多数情况 INNER JOIN
查询(一对多关系),我可以重写它们以使用 CROSS APPLY
, ,但它们总是给我等效的执行计划。
谁能给我一个很好的例子 CROSS APPLY
在这种情况下有所作为 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
在不简单的事情上更好 JOIN
健康)状况。
这个选择 3
最后记录 t2
对于每个记录 t1
:
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
是一个关于 20,000,000
记录 PRIMARY KEY
上 id
.
此查询:
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
在许多情况下我们需要替换 INNER JOIN
和 CROSS APPLY
.
1.根据 TOP n
结果
考虑是否需要选择 Id
和 Name
从 Master
和每个日期的最后两个日期 Id
从 Details table
.
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
以上查询会生成以下结果。
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
然后仅在外部查询中加入这些记录 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
并形成以下结果。
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
IE, WHERE M.ID=D.ID
.
2.当我们需要时 INNER JOIN
使用功能的功能。
CROSS APPLY
可以用作替代品 INNER JOIN
当我们需要从 Master
桌子和 function
.
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
)
产生以下结果
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)
带来结果
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,您想提出多个与年龄相关的字段,这些字段还将依靠其他数据源(例如就业),例如年龄,年龄组,ageathing,crimumentirementdate等,以用于最终用户应用程序(例如,Excel Pivottables)。
选项有限,很少优雅:
加入子查询不能基于父询问中的数据在数据集中引入新值(必须独立存在)。
UDF整洁,但由于倾向于防止平行操作而缓慢。而且,成为一个单独的实体可以是好(少代码)或坏(代码)。
接线表。有时他们可以工作,但是很快您就加入了很多工会。大混乱。
假设您的计算不需要在主查询中途获得的数据,则创建另一个单一的视图。
中介表。是的...这通常是有效的,通常是一个不错的选择,因为它们可以被索引和快速,但是由于更新语句不平行并且不允许级联公式(重复使用结果),因此性能也可以下降。相同的陈述。有时,您只想在一次通过时做事。
嵌套查询。是的,您可以在任何时候都将括号放在整个查询上,并将其用作子查询,您可以在其上操纵源数据和计算的字段。但是,您只能在丑陋之前做很多事情。十分难看。
重复代码。 3个长(case ... else ... end)语句的最大价值是什么?那将是可读的!
- 告诉您的客户自己计算该死的事情。
我错过了什么?可能,请随时发表评论。但是,嘿,Cross Apply在这种情况下就像是天赐之物:您只需添加一个简单的 CROSS APPLY (select tbl.value + 1 as someFormula) as crossTbl
和瞧!现在,您的新字段已经准备好实际使用,就像源数据中一直存在一样。
通过交叉应用引入的值可以...
- 用于创建一个或多个计算的字段,而无需在混音中添加性能,复杂性或可读性问题
- 与加入一样,随后的几个交叉应用陈述可以参考自己:
CROSS APPLY (select crossTbl.someFormula + 1 as someMoreFormula) as crossTbl2
- 您可以在随后的联接条件下使用十字架引入的值
- 作为奖励,有桌值的功能方面
dang,他们无能为力!
交叉应用也可以很好地与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的应用,它的超级简单:
SELECT *
FROM Customer
CROSS APPLY (
SELECT TOP 1 *
FROM Order
WHERE Order.CustomerId = Customer.CustomerId
ORDER BY OrderDate DESC
) T
交叉应用可用于替换您需要子查询的列的子查询
子查询
select * from person p where
p.companyId in(select c.companyId from company c where c.companyname like '%yyy%')
在这里,我将无法选择公司表的列,因此请使用Cross Apply
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,将在左侧的桌子上应用于每一行,这将是独一无二的。
当然,还有其他限制,即申请比其他朋友在上面发布的限制更好。
这是一篇文章,用他们的性能差异和对加入的用法来解释这一切。
如本文所建议的,对于正常的联接操作(内部和交叉),它们之间没有性能差异。
当您必须进行这样的查询时,使用差异到达:
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不能绑定。 在这里,随着每行的读取,该值将传递给函数。对我来说听起来很酷。 :)
好吧,我不确定这是否有资格使用交叉应用与内部加入的理由,但是在使用Cross Apply的论坛帖子中为我回答了此查询,因此我不确定是否使用Inner Join有一种平等的方法:
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
结尾
申请操作员的本质是允许从子句中的操作员的左侧和右侧之间的相关性。
与加入相反,不允许输入之间的相关性。
谈到申请操作员的相关性,我的意思是在右侧我们可以放置:
- 派生表格 - 作为与别名相关的子查询
- 表值函数 - 具有参数的概念视图,参数可以参考左侧
两者都可以返回多个列和行。
这也许是一个古老的问题,但我仍然喜欢交叉的力量,以简化逻辑的重复使用,并为结果提供“链接”机制。
我在下面提供了一个SQL小提琴,其中显示了一个简单的示例,说明了如何使用Cross Apply在数据集上执行复杂的逻辑操作,而不会完全弄乱。从这里进行更复杂的计算并不难。