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

Jef*_*ang 890 sql t-sql sql-server performance cross-apply

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

我已经阅读(模糊地,通过互联网上的帖子),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
Run Code Online (Sandbox Code Playgroud)

Qua*_*noi 641

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

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

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
Run Code Online (Sandbox Code Playgroud)

它不容易用INNER JOIN条件配制.

您可以使用CTE's&window函数执行类似的操作:

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
Run Code Online (Sandbox Code Playgroud)

,但这不太可读,可能效率较低.

更新:

刚检查过.

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
Run Code Online (Sandbox Code Playgroud)

运行几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
Run Code Online (Sandbox Code Playgroud)

是即时的.

  • 虽然这是最受欢迎的答案,但我认为它没有回答实际问题"使用CROSS APPLY的主要目的是什么?".主要目的是启用带有参数的表函数,每行执行一次,然后连接到结果. (359认同)
  • @MikeKulls是的,但是OP没有要求使用"CROSS APPLY"的主要目的,他要求何时选择"INNER JOIN",什么时候也可以. (14认同)
  • 值得一提的是,这在标准(ANSI)SQL中称为"横向连接" (7认同)
  • @Mike:你如何用`INNER JOIN'打电话给'TVF`? (5认同)
  • 请参阅Ariel链接的末尾。row_number()查询同样好,甚至不需要连接。因此,我不认为这种情况应该交叉使用(选择前3个,按t1.id进行分区)。 (2认同)
  • @Quassnoi-指向您博客文章的链接出现在下方。该帖子存档在这里:http://web.archive.org/web/20101225210315/http://explainextended.com/2009/07/16/inner-join-vs-cross-apply/ (2认同)

nur*_*tin 191

cross apply有时候你可以做你不能做的事情inner join.

示例(语法错误):

select F.* from sys.objects O  
inner join dbo.myTableFun(O.name) F   
on F.schema_id= O.schema_id
Run Code Online (Sandbox Code Playgroud)

这是一个语法错误,因为与inner join表函数一起使用时,只能将变量或常量作为参数.(即,表函数参数不能依赖于另一个表的列.)

然而:

select F.* from sys.objects O  
cross apply ( select * from dbo.myTableFun(O.name) ) F  
where F.schema_id= O.schema_id
Run Code Online (Sandbox Code Playgroud)

这是合法的.

编辑: 或者,更短的语法:(由ErikE)

select F.* from sys.objects O  
cross apply dbo.myTableFun(O.name) F
where F.schema_id= O.schema_id
Run Code Online (Sandbox Code Playgroud)

编辑:

注意:Informix 12.10 xC2 +具有横向派生表,Postgresql(9.3+)具有横向子查询,可用于类似的效果.

  • 在`CROSS APPLY`中不需要`SELECT`.请尝试`CROSS APPLY dbo.myTableFun(O.name)F`. (14认同)
  • 我认为这就是为什么我们交叉申请的原因.如果你查看下面的链接,这是MS关于交叉申请的第一件事.它可能有其他用途,但我认为这是它被引入的原因.没有它,表函数在很多情况下都不可用.http://technet.microsoft.com/en-us/library/ms175156.aspx (11认同)
  • @ErikE 当然,你总是可以使用不太灵活的语法来交叉应用。我展示了更通用的版本,您有时可以使用它以避免将难以计算的列带入查询中。 (2认同)
  • 如果表函数参数取决于外部选择中另一个表的列(也称为外部引用),则@Bolu内部联接将不起作用。如果表函数参数是文字或变量,它将起作用。交叉应用将在两种情况下均起作用。 (2认同)

Sar*_*avu 160

考虑你有两张桌子.

主表

x------x--------------------x
| Id   |        Name        |
x------x--------------------x
|  1   |          A         |
|  2   |          B         |
|  3   |          C         |
x------x--------------------x
Run Code Online (Sandbox Code Playgroud)

详情表

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
Run Code Online (Sandbox Code Playgroud)

在有些情况下,我们需要更换很多情况下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
Run Code Online (Sandbox Code Playgroud)

以上查询生成以下结果.

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
Run Code Online (Sandbox Code Playgroud)

看,它生成了最后两个日期的最后两个日期的结果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
Run Code Online (Sandbox Code Playgroud)

并形成以下结果.

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
Run Code Online (Sandbox Code Playgroud)

这是它的工作原理.里面的查询CROSS APPLY可以引用外部表,在那里INNER JOIN不能这样做(它抛出编译错误).当找到最后两个日期时,加入在内部完成,CROSS APPLYWHERE M.ID=D.ID.

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

CROSS APPLYINNER JOIN当我们需要从Master表和a 获得结果时,可以用作替代function.

SELECT M.ID,M.NAME,C.PERIOD,C.QTY
FROM MASTER M
CROSS APPLY dbo.FnGetQty(M.ID) C
Run Code Online (Sandbox Code Playgroud)

这是功能

CREATE FUNCTION FnGetQty 
(   
    @Id INT 
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT ID,PERIOD,QTY 
    FROM DETAILS
    WHERE ID=@Id
)
Run Code Online (Sandbox Code Playgroud)

产生了以下结果

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
Run Code Online (Sandbox Code Playgroud)

交叉应用的额外优势

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
Run Code Online (Sandbox Code Playgroud)

查询如下.

SELECT DISTINCT ID,DATES
FROM MYTABLE 
CROSS APPLY(VALUES (FROMDATE),(TODATE))
COLUMNNAMES(DATES)
Run Code Online (Sandbox Code Playgroud)

这会带给你结果

  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
Run Code Online (Sandbox Code Playgroud)

  • 这个答案证明了向下滚动页面确实值得,而不仅仅是选择接受的页面. (9认同)
  • 2对4记录的优秀示例,帮助我理解了需要这样做的背景. (4认同)
  • 到目前为止,最好的示例来解释APPLY的用法...我已经阅读了许多帖子,并意识到这种解释使图片看起来像水一样。非常感谢兄弟。 (2认同)

mto*_*one 40

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

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

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

Dang,他们无能为力!

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

小智 14

交叉应用也适用于XML字段.如果要与其他字段一起选择节点值.

例如,如果您有一个包含某些xml的表

<root>
    <subnode1>
       <some_node value="1" />
       <some_node value="2" />
       <some_node value="3" />
       <some_node value="4" />
    </subnode1>
</root>
Run Code Online (Sandbox Code Playgroud)

使用查询

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
Run Code Online (Sandbox Code Playgroud)

将返回结果

xt_id root_attribute_value node_attribute_value lt_name
----------------------------------------------------------------------
1     test1            1                    Benefits
1     test1            4                    FINRPTCOMPANY
Run Code Online (Sandbox Code Playgroud)


小智 12

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

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

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

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


Jez*_*Jez 9

.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;
Run Code Online (Sandbox Code Playgroud)


小智 8

这里有一篇文章解释了这一切,以及它们与 JOINS 的性能差异和用法。

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

正如本文所建议的,对于正常的连接操作(INNER AND 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)
Run Code Online (Sandbox Code Playgroud)

也就是说,当你必须与功能联系起来时。使用 INNER JOIN 无法完成此操作,否则会出现错误“无法绑定多部分标识符“D.DepartmentID””。这里,当读取每一行时,值被传递给函数。对我来说听起来很酷。:)


小智 7

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

子查询

select * from person p where
p.companyId in(select c.companyId from company c where c.companyname like '%yyy%')
Run Code Online (Sandbox Code Playgroud)

在这里,我将无法选择公司表的列,因此,使用交叉申请

select P.*,T.CompanyName
from Person p
cross apply (
    select *
    from Company C
    where p.companyid = c.companyId and c.CompanyName like '%yyy%'
) T
Run Code Online (Sandbox Code Playgroud)


sha*_*esh 5

我想应该是可读性;)

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

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


Raf*_*Raf 5

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

与 JOIN 相比,不允许输入之间存在关联。

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

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

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