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

我已经(模糊地通过Internet上的帖子)阅读到,如果要分区,则在选择大型数据集时cross apply可以更有效。 (想到了分页)

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

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

谁能给我一个很好的例子,说明CROSS APPLYINNER JOIN也能正常工作的情况下何时有所作为?


编辑: br />这是一个简单的示例,其中执行计划完全相同。 (向我展示它们的不同之处以及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


评论

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

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

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

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

CROSS APPLY在允许一个集依赖于另一个集方面有明显的用法(与JOIN运算符不同),但这并不是没有代价的:它的行为就像一个对左集的每个成员进行操作的函数,因此在SQL中服务器认为它总是执行循环连接,这几乎绝不是连接集合的最佳方法。因此,在需要时使用APPLY,但不要对JOIN过度使用它。

#1 楼


谁能给我一个很好的例子,说明在CROSS APPLY在INNER JOIN也能起作用的情况下有所作为吗?比较:


INNER JOINCROSS APPLY

CROSS APPLY在没有简单JOIN条件的情况下效果更好。来自3的最后一条记录,来自t2的每条记录:类似于使用t1和window函数的功能: />
只需检查一下。

INNER JOIN是一个约CTE记录的表,在master上带有20,000,000

此查询:

SELECT  t1.*, t2o.*
FROM    t1
CROSS APPLY
        (
        SELECT  TOP 3 *
        FROM    t2
        WHERE   t2.t1_id = t1.id
        ORDER BY
                t2.rank DESC
        ) t2o


运行了将近PRIMARY KEY秒,而这一个:

q431 2078q

是即时的。

评论


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

–杨杰夫·肉丸(Jeff Meatball Yang)
09年7月16日在17:58

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

– MikeKulls
2011年8月12日,0:40

@Mike:您如何通过INNER JOIN呼叫TVF?

– Quassnoi
11年8月12日在1:09

@MikeKulls是的,但是OP并没有要求使用CROSS APPLY的主要目的,他要求何时在INNER JOIN上选择它,何时可行。

– ErikE
13年4月23日在21:51

值得一提的是,这在标准(ANSI)SQL中称为横向连接

– a_horse_with_no_name
15年1月8日在11:05

#2 楼

cross apply有时使您能够执行inner join不能执行的操作。

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

但是:

select F.* from sys.objects O  
inner join dbo.myTableFun(O.name) F   
on F.schema_id= O.schema_id


这是合法的。

编辑:
或者使用较短的语法:(由ErikE提供)

select F.* from sys.objects O  
cross apply ( select * from dbo.myTableFun(O.name) ) F  
where F.schema_id= O.schema_id


编辑:

注意: br /> Informix 12.10 xC2 +具有横向衍生表,而Postgresql(9.3+)具有横向子查询,可以使用类似的效果。

评论


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

– MikeKulls
2011年8月12日在0:38



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

– Nurettin
2011年11月19日上午10:10

交叉应用内无需选择。请尝试交叉应用dbo.myTableFun(O.name)F。

– ErikE
2012-12-27 3:14



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

– Nurettin
13年4月20日在11:21

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

– Nurettin
15年6月18日在7:20



#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中选择NameMaster,并从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的外部查询中加入这些记录,这是错误的。这应该同时返回Ids 1和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



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



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可以替代UNPIVOTCROSS 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



SQL FIDDLE

为您带来结果

SELECT DISTINCT ID,DATES
FROM MYTABLE 
CROSS APPLY(VALUES (FROMDATE),(TODATE))
COLUMNNAMES(DATES)


评论


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

– trnelson
17年2月20日在15:13

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

– Mostafa Armandi
18-09-5在4:41

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

– AG7
18年11月24日在7:12

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

–赵Jo
19年3月21日在18:01

#4 楼

在我看来,CROSS APPLY在处理复杂/嵌套查询中的计算字段时可以填补一定的空白,并使它们更简单易读。提供了多个与年龄相关的字段,这些字段也将依赖其他数据源(例如,就业),例如Age,AgeGroup,AgeAtHiring,MinimumRetirementDate等,供最终用户应用程序使用(例如Excel PivotTables)。

选项是有限的并且很少优雅:


JOIN子查询不能基于父查询中的数据在数据集中引入新值(必须独立存在)。 br /> UDF很整洁,但速度较慢,因为它们往往会阻止并行操作。作为一个单独的实体可以是一件好事(更少的代码)也可以是一件坏事(代码在哪里)。
结点表。有时它们可​​以工作,但是很快您就可以与大量UNION一起加入子查询。大麻烦。
创建另一个单一用途的视图,假设您的计算不需要在主查询过程中获取的数据。
中间表。是的...通常可以正常工作,并且通常是一个不错的选择,因为它们可以被索引并且快速,但是由于UPDATE语句不平行并且不允许级联公式(重用结果)来更新内部的多个字段,因此性能也会下降相同的声明。有时您只想一遍就做。
嵌套查询。是的,您可以随时在整个查询上加上括号并将其用作子查询,在子查询上您可以操作源数据和计算字段。但是您只能在丑陋之前做很多事情。非常难看。

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


告诉您的客户自己计算该死的东西。



我错过了什么?可能吧,请随时发表评论。但是,在这种情况下,CROSS APPLY就像天赐之物:您只需添加一个简单的CROSS APPLY (select tbl.value + 1 as someFormula) as crossTbl和voilà!现在,您的新字段几乎可以像在源数据中一样使用了。

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


用于创建一个或多个计算字段而不会增加混合的性能,复杂性或可读性问题
与JOIN一样,随后的一些CROSS APPLY语句可以引用自己:由CROSS APPLY在随后的JOIN条件中引入的
作为奖励,它具有表值函数方面

Dang,没有什么可以做的!

评论


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

–mrmillsy
2015年10月6日,下午1:28

有关如何使用Cross Apply替代CASE..ELSE..END的更多信息/示例?

–przemo_li
16 Sep 12'在14:23

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

–mtone
16/09/12在20:12



交叉应用计算示例col.sql.drylib.com

– alpav
8月13日19:31

#5 楼

从技术上来说,这已经得到很好的回答,但是让我举一个具体的例子说明它是如何非常有用的:让我们说您有两个表,Customer和Order。客户有很多订单。

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

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


#6 楼

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


#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


#8 楼

我想应该是可读性;)

交叉应用对于阅读的人来说有些独特,告诉他们正在使用UDF,它将应用于左侧表格的每一行。 />
当然,还有其他限制,那就是CROSS APPLY比上面其他朋友已发布的JOIN更好地使用。

#9 楼

这是一篇文章,对它们进行了说明,并介绍了它们在JOINS上的性能差异和用法。对于正常的联接操作(内部和交叉)而言,它们之间的性能差异没有任何区别。 >
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”。”此处,在读取每一行时,会将值传递给函数。对我来说听起来很酷。 :)

#10 楼

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

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 楼

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

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

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


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

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

#12 楼

这也许是一个古老的问题,但是我仍然喜欢CROSS APPLY的功能,它可以简化逻辑的重用并提供结果的“链接”机制。下面显示了一个简单的示例,说明如何使用CROSS APPLY对数据集执行复杂的逻辑运算而不会造成任何混乱。从这里推断出更复杂的计算并不难。

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

#13 楼

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

#14 楼

这是一个简短的教程,可以保存在.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;