什么时候应该在内部联接上使用交叉应用?


925

使用CROSS APPLY的主要目的是什么?

我已经读过(模糊地通过Internet上的帖子),cross apply如果您要进行分区,则在选择大型数据集时可能会更有效率。(想起分页)

我也知道,CROSS APPLY不需要UDF作为右表。

在大多数INNER JOIN查询(一对多关系)中,我可以将它们重写为use 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

50
我知道这对我来说甚至更挑剔,但“表演者”绝对是一个词。只是与效率无关。
Rire1979

2
对于sql xquery非常有用。检查一下
ARZ

3
似乎使用“内循环连接”将非常接近交叉应用。希望您的示例详细说明哪个连接提示是等效的。只是说联接可能导致内部/循环/合并甚至“其他”,因为它可能会与其他联接重新排列。
crokusek 2012年

3
如果联接将创建很多行,但是您一次只需要评估一个行联接。我有一个案例,我需要在具有1亿多行的表上进行自我连接,并且内存根本不够。因此,我使用游标降低了内存占用量。从游标开始,我继续应用仍然托管的内存占用,并且比游标快1/3。
狗仔队2012年

10
CROSS APPLY在允许一个集合依赖另一个集合上有其明显的用法(不同于JOIN运算符),但这并不是没有代价的:它的行为就像一个对集合的每个成员进行操作的函数,因此,按照SQL Server的说法,总是执行Loop Join,这几乎绝不是加入集合的最佳方法。因此,请APPLY在需要时使用,但不要对过度使用它JOIN
2014年

Answers:


667

在CROSS APPLY在INNER JOIN也将起作用的情况下,CROSS APPLY有所作为时,谁能给我一个很好的例子?

有关性能比较的详细信息,请参阅我博客中的文章:

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 KEYid

该查询:

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

是即时的。


2
请参阅Ariel链接的末尾。row_number()查询同样好,甚至不需要连接。因此,我不认为这种情况应该交叉使用(选择前3个,按t1.id进行分区)。
Jeff Meatball Yang

375
尽管这是最流行的答案,但我认为它不能回答实际的问题“使用CROSS APPLY的主要目的是什么?”。主要目的是使带有参数的表函数每行执行一次,然后加入结果。
MikeKulls 2011年

5
@Mike:您如何称呼TVFwith INNER JOIN
Quassnoi

15
@MikeKulls是的,但是OP并没有要求使用的主要目的CROSS APPLY,他要求何时选择它INNER JOIN,什么时候也可以使用。
ErikE

8
值得一提的是,这被称为lateral join标准(ANSI)SQL
a_horse_with_no_name 2015年

198

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+)具有横向子查询,可以使用类似的效果。


11
我认为这就是我们交叉申请的背后原因。如果您查看下面的链接,这是MS关于交叉申请的第一句话。它可能还有其他用途,但我认为这就是它被引入的原因。没有它,表函数将在许多情况下不可用。 technet.microsoft.com/zh-CN/library/ms175156.aspx
MikeKulls,2011年

当与内联表函数结合使用时,交叉应用还可以产生一个不错的执行计划,同时保持非常需要的模块化。
nurettin

14
SELECT里面不需要CROSS APPLY。请尝试CROSS APPLY dbo.myTableFun(O.name) F
ErikE 2012年

1
@ErikE当然,您始终可以使用不太灵活的语法来交叉应用。我展示了一个更通用的版本,您有时可以使用它以避免将难以计算的列带入查询。
nurettin

2
如果表函数参数取决于外部选择中另一个表的列(也称为外部引用),则@Bolu内部联接将不起作用。如果表函数参数是文字或变量,它将起作用。交叉应用将在两种情况下均起作用。
nurettin

175

考虑您有两个表。

主表

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 JOINCROSS APPLY

1.根据TOP n结果联接两个表

试想,如果我们需要选择IdNameMaster和最后两个日期为每个IdDetails 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,这是错误的。这应该同时返回Ids1和2,但它仅返回1,因为1具有最后两个日期。为此,我们需要使用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即在内完成加入WHERE M.ID=D.ID

2.当我们需要INNER JOIN使用功能的功能时。

CROSS APPLYINNER JOIN当需要从Mastertable和a 获取结果时,可以用作替代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 APPLYOUTER 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

4
2 vs 4记录的出色示例,帮助我了解了需要使用此记录的环境。
trnelson

13
该答案证明,向下滚动页面而不是仅选择可接受的页面确实值得。
Mostafa Armandi

2
到目前为止,最好的示例来解释APPLY的用法...我已经阅读了许多帖子,并意识到这种解释使图片看起来像水一样。非常感谢兄弟。
AG7

1
对于点1,我们有2行ID 1,而不是4行ID 1,2。我们不是只使用左联接。
Joseph Cho

43

在我看来,CROSS APPLY在处理复杂/嵌套查询中的计算字段时可以填补一定的空白,并使它们更简单易读。

举个简单的例子:您有一个DoB,并且想要显示多个与年龄相关的字段,这些字段也将依赖于其他数据源(例如,就业),例如Age,AgeGroup,AgeAtHiring,MinimumRetirementDate等,供最终用户应用程序使用(例如,Excel PivotTables)。

选项是有限的,很少是优雅的:

  • JOIN子查询不能基于父查询中的数据在数据集中引入新值(它必须独立存在)。

  • UDF整洁,但速度较慢,因为它们倾向于阻止并行操作。作为一个单独的实体可以是一件好事(更少的代码)也可以是一件坏事(代码在哪里)。

  • 连接表。有时它们可​​以工作,但是很快您就可以与大量UNION一起加入子查询。大混乱。

  • 假设您的计算不需要在主查询过程中获取的数据,则可以创建另一个视图。

  • 中间表。是的...通常可以正常工作,并且通常是一个不错的选择,因为它们可以被索引并且速度很快,但是由于UPDATE语句不平行并且不允许级联公式(重用结果)来更新内部的多个字段,因此性能也会下降相同的声明。有时,您只喜欢一次性完成任务。

  • 嵌套查询。是的,您随时都可以在整个查询上加上括号并将其用作子查询,在子查询上您可以操作源数据和计算字段。但是您只能在丑陋之前做很多事情。非常难看。

  • 重复代码。3个长(CASE ... ELSE ... END)语句的最大值是多少?那将是可读的!

    • 告诉您的客户自己计算该死的事情。

我错过了什么?可能吧,随时发表评论。但是,在这种情况下,CROSS APPLY就像天赐之物:您只需添加一个简单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引入的值
  • 作为奖励,还有表值函数方面

ang,他们无能为力!


1
这对我来说是很大的+1,我很惊讶没有经常提到它。也许您可以扩展此示例,以显示如何对派生值链执行“过程”计算?例如:交叉应用(选择crossTbl.value * tbl.multiplier作为乘数)multiTbl-交叉应用(选择multiTbl.Multiplied / tbl.DerivativeRatio作为衍生)得出的Tbl-等...
mrmillsy 2015年

1
有关如何使用Cross Apply替代CASE..ELSE..END的更多信息/示例?
przemo_li 2016-09-12

3
@przemo_li APPLY可用于存储case语句的结果(除其他外),以​​便引用它。结构可能类似于:subquery.intermediateResult> 0时为SELECT CASE,然后从someTable外部应用(是,选择CASE ... END ... ELSE作为中间结果)为“是”否则为“否”作为子查询。
mtone

14

交叉应用同样适用于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

13

从技术上讲,这已经很好地解决了,但是让我举一个具体的例子说明它是多么有用:

假设您有两个表,客户表和订单。客户有很多订单。

我想创建一个视图,向我提供有关客户以及他们最近完成的订单的详细信息。仅使用JOINS,这将需要一些自联接和聚合,这并不理想。但是使用Cross Apply,它超级简单:

SELECT *
FROM Customer
CROSS APPLY (
  SELECT TOP 1 *
  FROM Order
  WHERE Order.CustomerId = Customer.CustomerId
  ORDER BY OrderDate DESC
) T

7

交叉应用可用于替换需要子查询列的子查询

子查询

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

5

我想应该是可读性;)

对于那些想告诉他们正在使用UDF的人来说,CROSS APPLY在某种程度上将是唯一的,它将应用于左侧表格的每一行。

当然,还有其他一些限制,那就是使用CROSS APPLY比使用其他好友在上面发布的JOIN更好。


4

这是一篇介绍所有内容的文章,它们的性能差异和与JOINS的用法不同。

SQL Server通过联接进行交叉应用和外部应用

正如本文所建议的,它们之间的正常联接操作(内部和交叉)没有性能差异。

在此处输入图片说明

使用差异会在您必须执行以下查询时到达:

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”。” 此处,在读取每一行时,会将值传递给函数。对我来说听起来很酷。:)


3

好吧,我不确定这是否符合使用交叉申请与内部联接的理由,但是在使用交叉申请的论坛帖子中为我回答了此查询,因此我不确定是否使用内部联接的均等方法:

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

结束


3

APPLY运算符的本质是允许FROM子句中运算符的左侧和右侧之间具有相关性。

与JOIN相比,输入之间的相关性是不允许的。

说到APPLY运算符中的相关性,我的意思是在右侧我们可以输入:

  • 派生表-作为具有别名的相关子查询
  • 表值函数-具有参数的概念视图,其中参数可以引用左侧

两者都可以返回多个列和行。


2

这也许是一个古老的问题,但是我仍然喜欢CROSS APPLY的功能,它可以简化逻辑的重用并提供结果的“链接”机制。

我在下面提供了一个SQL Fiddle,它显示了一个简单的示例,说明如何使用CROSS APPLY对数据集执行复杂的逻辑操作而不会造成任何混乱。从这里不难推断出更复杂的计算。

http://sqlfiddle.com/#!3/23862/2


1

虽然大多数使用CROSS APPLY的查询都可以使用INNER JOIN进行重写,但是CROSS APPLY可以产生更好的执行计划和更好的性能,因为它可以限制联接之前尚未联接的集合。

这里被盗

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.