什么时候应该使用交叉申请而不是内部联接?

时间:2009-07-16 17:42:22

标签: sql sql-server performance tsql cross-apply

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

我已经阅读(隐约地,通过互联网上的帖子),如果您正在进行分区,cross apply在选择大型数据集时可以更有效率。 (寻找灵感)

我也知道CROSS APPLY doesn't require a UDF as the right-table.

在大多数INNER JOIN个查询(一对多关系)中,我可以重写它们以使用CROSS APPLY,但它们总是给我相同的执行计划。

有人能给我一个很好的例子,说明CROSS APPLYINNER 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

16 个答案:

答案 0 :(得分:619)

  

任何人都可以给我一个很好的例子,说明当CRNER APPLY在INNER JOIN也会起作用的情况下有所作为吗?

请参阅我博客中的文章,了解详细的效果比较:

CROSS APPLY可以更好地处理没有简单JOIN条件的事情。

此选项从3t2的每条记录选择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 秒,而这一秒:

{{1}}

是即时的。

答案 1 :(得分:187)

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 +有Lateral Derived Tables,Postgresql(9.3+)有Lateral Subqueries,可以用来产生类似的效果。

答案 2 :(得分:143)

考虑你有两张桌子。

MASTER TABLE

x------x--------------------x
| Id   |        Name        |
x------x--------------------x
|  1   |          A         |
|  2   |          B         |
|  3   |          C         |
x------x--------------------x

DETAILS TABLE

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。根据{{​​1}}结果

加入两个表格

考虑我们是否需要从TOP n中选择IdName以及从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

<强> 2。当我们需要使用函数的WHERE M.ID=D.ID功能时。

当我们需要从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的替代品。这里可以使用UNPIVOTCROSS 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)

答案 3 :(得分:37)

在我看来,在使用复杂/嵌套查询中的计算字段时,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和vo!您的新字段现在可以使用,就像源数据中一直存在的那样。

通过CROSS APPLY引入的值可以......

  • 用于创建一个或多个计算字段,而不会向混合中添加性能,复杂性或可读性问题
  • 与JOIN一样,几个后续的CROSS APPLY语句可以引用自己:CROSS APPLY (select crossTbl.someFormula + 1 as someMoreFormula) as crossTbl2
  • 您可以在后续的JOIN条件中使用CROSS APPLY引入的值
  • 作为奖励,有表值函数方面
但是,他们没有什么不能做的!

答案 4 :(得分:36)

这是一个CROSS APPLY与性能产生巨大差异的例子:

Using CROSS APPLY to optimize joins on BETWEEN conditions

请注意,除了替换内部联接之外,您还可以重用代码,例如截断日期,而不会因为使用标量UDF而牺牲性能损失,例如:Calculating third Wednesday of the month with inline UDFs

答案 5 :(得分: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

答案 6 :(得分:6)

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

子查询

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

答案 7 :(得分:5)

我想它应该是可读性的;)

CROSS APPLY对于阅读的人来说有点独特,告诉他们正在使用UDF,它将应用于左侧表格中的每一行。

当然,还有其他限制,其中CROSS APPLY比其他朋友在上面发布的JOIN更好用。

答案 8 :(得分:4)

这篇文章解释了这一切,其性能差异和使用情况超过了JOINS。

SQL Server CROSS APPLY and OUTER APPLY over JOINS

正如本文所述,正常连接操作(内部和交叉)之间没有性能差异。

enter image description here

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

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“无法绑定。” 这里传递的值读取每一行的功能。听起来很酷我。 :)

答案 9 :(得分:4)

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

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

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

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

答案 10 :(得分:3)

嗯我不确定这是否有资格作为使用Cross Apply和Inner Join的理由,但是这个查询是在使用Cross Apply的论坛帖子中为我回答的,所以我不确定是否有使用Inner的等效方法加入:

Create PROCEDURE [dbo].[Message_FindHighestMatches]

-- Declare the Topical Neighborhood
@TopicalNeighborhood nchar(255)

AS BEGIN

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

END

答案 11 :(得分:2)

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

Here被盗

答案 12 :(得分:1)

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

我在下面提供了一个SQL小提琴,其中显示了一个简单的示例,说明如何使用CROSS APPLY对数据集执行复杂的逻辑操作,而不会产生任何麻烦。从这里推断更复杂的计算并不难。

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

答案 13 :(得分:1)

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

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

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

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

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

答案 14 :(得分:1)

这是一个简短的教程,可以保存在.sql文件中并在SSMS中执行,我为自己编写了该教程,以快速刷新有关CROSS APPLY的工作方式和使用时间的记忆:

-- Here's the key to understanding CROSS APPLY: despite the totally different name, think of it as being like an advanced 'basic join'.
-- A 'basic join' gives the Cartesian product of the rows in the tables on both sides of the join: all rows on the left joined with all rows on the right.
-- The formal name of this join in SQL is a CROSS JOIN.  You now start to understand why they named the operator CROSS APPLY.

-- Given the following (very) simple tables and data:
CREATE TABLE #TempStrings ([SomeString] [nvarchar](10) NOT NULL);
CREATE TABLE #TempNumbers ([SomeNumber] [int] NOT NULL);
CREATE TABLE #TempNumbers2 ([SomeNumber] [int] NOT NULL);
INSERT INTO #TempStrings VALUES ('111'); INSERT INTO #TempStrings VALUES ('222');
INSERT INTO #TempNumbers VALUES (111); INSERT INTO #TempNumbers VALUES (222);
INSERT INTO #TempNumbers2 VALUES (111); INSERT INTO #TempNumbers2 VALUES (222); INSERT INTO #TempNumbers2 VALUES (222);

-- Basic join is like CROSS APPLY; 2 rows on each side gives us an output of 4 rows, but 2 rows on the left and 0 on the right gives us an output of 0 rows:
SELECT
    st.SomeString, nbr.SomeNumber
FROM -- Basic join ('CROSS JOIN')
    #TempStrings st, #TempNumbers nbr
    -- Note: this also works:
    --#TempStrings st CROSS JOIN #TempNumbers nbr

-- Basic join can be used to achieve the functionality of INNER JOIN by first generating all row combinations and then whittling them down with a WHERE clause:
SELECT
    st.SomeString, nbr.SomeNumber
FROM -- Basic join ('CROSS JOIN')
    #TempStrings st, #TempNumbers nbr
WHERE
    st.SomeString = nbr.SomeNumber

-- However, for increased readability, the SQL standard introduced the INNER JOIN ... ON syntax for increased clarity; it brings the columns that two tables are
-- being joined on next to the JOIN clause, rather than having them later on in the WHERE clause.  When multiple tables are being joined together, this makes it
-- much easier to read which columns are being joined on which tables; but make no mistake, the following syntax is *semantically identical* to the above syntax:
SELECT
    st.SomeString, nbr.SomeNumber
FROM -- Inner join
    #TempStrings st INNER JOIN #TempNumbers nbr ON st.SomeString = nbr.SomeNumber

-- Because CROSS APPLY is generally used with a subquery, the subquery's WHERE clause will appear next to the join clause (CROSS APPLY), much like the aforementioned
-- 'ON' keyword appears next to the INNER JOIN clause.  In this sense, then, CROSS APPLY combined with a subquery that has a WHERE clause is like an INNER JOIN with
-- an ON keyword, but more powerful because it can be used with subqueries (or table-valued functions, where said WHERE clause can be hidden inside the function).
SELECT
    st.SomeString, nbr.SomeNumber
FROM
    #TempStrings st CROSS APPLY (SELECT * FROM #TempNumbers tempNbr WHERE st.SomeString = tempNbr.SomeNumber) nbr

-- CROSS APPLY joins in the same way as a CROSS JOIN, but what is joined can be a subquery or table-valued function.  You'll still get 0 rows of output if
-- there are 0 rows on either side, and in this sense it's like an INNER JOIN:
SELECT
    st.SomeString, nbr.SomeNumber
FROM
    #TempStrings st CROSS APPLY (SELECT * FROM #TempNumbers tempNbr WHERE 1 = 2) nbr

-- OUTER APPLY is like CROSS APPLY, except that if one side of the join has 0 rows, you'll get the values of the side that has rows, with NULL values for
-- the other side's columns.  In this sense it's like a FULL OUTER JOIN:
SELECT
    st.SomeString, nbr.SomeNumber
FROM
    #TempStrings st OUTER APPLY (SELECT * FROM #TempNumbers tempNbr WHERE 1 = 2) nbr

-- One thing CROSS APPLY makes it easy to do is to use a subquery where you would usually have to use GROUP BY with aggregate functions in the SELECT list.
-- In the following example, we can get an aggregate of string values from a second table based on matching one of its columns with a value from the first
-- table - something that would have had to be done in the ON clause of the LEFT JOIN - but because we're now using a subquery thanks to CROSS APPLY, we
-- don't need to worry about GROUP BY in the main query and so we don't have to put all the SELECT values inside an aggregate function like MIN().
SELECT
    st.SomeString, nbr.SomeNumbers
FROM
    #TempStrings st CROSS APPLY (SELECT SomeNumbers = STRING_AGG(tempNbr.SomeNumber, ', ') FROM #TempNumbers2 tempNbr WHERE st.SomeString = tempNbr.SomeNumber) nbr
-- ^ First the subquery is whittled down with the WHERE clause, then the aggregate function is applied with no GROUP BY clause; this means all rows are
--   grouped into one, and the aggregate function aggregates them all, in this case building a comma-delimited string containing their values.

DROP TABLE #TempStrings;
DROP TABLE #TempNumbers;
DROP TABLE #TempNumbers2;

答案 15 :(得分:0)

我们使用 CROSS APPLY 从另一个(更新请求)表中使用 JSON 更新表 -- 连接不适用于此,因为我们使用 OPENJSON 来读取JSON 的内容,而 OPENJSON 是一个“表值函数”。

我打算在此处放一个我们的 UPDATE 命令的简化版本作为示例,但即使是简化版本,作为示例,它也相当大且过于复杂。因此,仅对命令的一部分进行如此简单的“草图”就足够了:

SELECT  
       r.UserRequestId,
       j.xxxx AS xxxx,
FROM  RequestTable as r WITH (NOLOCK)
   CROSS APPLY
      OPENJSON(r.JSON, '$.requesttype.recordtype')
      WITH(
             r.userrequestid nvarchar(50) '$.userrequestid',
             j.xxx nvarchar(20) '$.xxx
           )j
       WHERE r.Id > @MaxRequestId
          and ... etc. ....