真实的例子,在SQL中何时使用OUTER / CROSS APPLY

我一直在和一位同事一起研究CROSS / OUTER APPLY ,我们正在努力寻找使用它们的真实例子。

我花了相当多的时间看我什么时候应该使用内部join交叉应用? 和谷歌search,但主要(唯一)的例子似乎很奇怪(使用表中的行数来确定从另一个表中select多less行)。

我认为这种情况可能会从OUTER APPLY受益:

联系人表(每个联系人包含1条logging)通讯条目表(每个联系人可包含n个电话,传真,电子邮件)

但是使用子查询,公用表expression式,带RANK()OUTER APPLY OUTER JOIN似乎都是一样的。 我猜这意味着这种情况不适用于APPLY

请分享一些真实生活的例子,并帮助解释function!

APPLY一些用途是…

1) 每组查询的前N个 (对于某些基数可以更高效)

 SELECT pr.name, pa.name FROM sys.procedures pr OUTER APPLY (SELECT TOP 2 * FROM sys.parameters pa WHERE pa.object_id = pr.object_id ORDER BY pr.name) pa ORDER BY pr.name, pa.name 

2)为外部查询中的每一行调用一个表值函数

 SELECT * FROM sys.dm_exec_query_stats AS qs CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) 

3) 重用列别名

 SELECT number, doubled_number, doubled_number_plus_one FROM master..spt_values CROSS APPLY (SELECT 2 * CAST(number AS BIGINT)) CA1(doubled_number) CROSS APPLY (SELECT doubled_number + 1) CA2(doubled_number_plus_one) 

4) 重新指定多个列组

假设1NF违反表结构….

 CREATE TABLE T ( Id INT PRIMARY KEY, Foo1 INT, Foo2 INT, Foo3 INT, Bar1 INT, Bar2 INT, Bar3 INT ); 

使用2008+ VALUES语法的示例

 SELECT Id, Foo, Bar FROM T CROSS APPLY (VALUES(Foo1, Bar1), (Foo2, Bar2), (Foo3, Bar3)) V(Foo, Bar); 

在2005年, UNION ALL可以用来代替。

 SELECT Id, Foo, Bar FROM T CROSS APPLY (SELECT Foo1, Bar1 UNION ALL SELECT Foo2, Bar2 UNION ALL SELECT Foo3, Bar3) V(Foo, Bar); 

在各种情况下,您无法避免使用CROSS APPLYOUTER APPLY

考虑你有两个表。

主表

 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 

交叉应用

有很多情况下,我们需要用CROSS APPLYreplaceINNER JOIN

1.如果我们想要使用INNER JOINfunction在TOP n结果中join2个表格

考虑是否需要从Master Details tableselectIdName ,并从Details table为每个Idselect最后两个date。

 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 

看,它产生的最后两个date的最后两个date的结果,然后join这些logging只在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不能这样做(抛出编译错误)。 在find最后两个date时,join是在CROSS APPLY内完成的,即WHERE M.ID=D.ID

2.当我们需要INNER JOINfunction使用function。

当我们需要从Master表和一个function得到结果时, CROSS APPLY可以用作INNER JOIN的replace。

 SELECT M.ID,M.NAME,C.PERIOD,C.QTY FROM MASTER M CROSS APPLY dbo.FnGetQty(M.ID) C 

这是function

 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 

外部应用

1.如果我们想要使用LEFT JOINfunction在TOP n结果中join2个表格

考虑是否需要从Master表中selectID和名称,并从Details表中为每个IDselect最后两个date。

 SELECT M.ID,M.NAME,D.PERIOD,D.QTY FROM MASTER M LEFT 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 | | 2 | B | NULL | NULL | | 3 | C | NULL | NULL | x------x---------x--------------x-------x 

这将带来错误的结果,也就是说,即使我们joinId ,它也将只带来来自Details表的最新的两个date数据。 所以正确的解决scheme是使用OUTER APPLY

 SELECT M.ID,M.NAME,D.PERIOD,D.QTY FROM MASTER M OUTER 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 | | 3 | C | NULL | NULL | x------x---------x--------------x-------x 

2.当我们需要使用functions LEFT JOIN functions

当我们需要从Master表和一个function得到结果时, OUTER APPLY可以用作LEFT JOIN的replace。

 SELECT M.ID,M.NAME,C.PERIOD,C.QTY FROM MASTER M OUTER APPLY dbo.FnGetQty(M.ID) C 

而function在这里。

 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 | | 3 | C | NULL | NULL | x------x---------x--------------x-------x 

CROSS APPLYOUTER APPLY共同特点

CROSS APPLYOUTER APPLY可用于在未转换时保留NULL值,这些值是可互换的。

考虑你有下面的表格

 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 

当您使用UNPIVOTFROMDATE AND TODATE为一列时,默认情况下它将消除NULL值。

 SELECT ID,DATES FROM MYTABLE UNPIVOT (DATES FOR COLS IN (FROMDATE,TODATE)) P 

产生下面的结果。 请注意,我们已经错过了Id号码3的logging

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

在这种情况下, CROSS APPLYOUTER APPLY将会有用

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

形成以下结果并保留其值为3 Id

  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 

一个真实的例子是如果你有一个调度器,并想看看每个计划任务的最近日志条目是什么。

 select t.taskName, lg.logResult, lg.lastUpdateDate from task t cross apply (select top 1 taskID, logResult, lastUpdateDate from taskLog l where l.taskID = t.taskID order by lastUpdateDate desc) lg 

回答上面的问题就是一个例子:

 create table #task (taskID int identity primary key not null, taskName varchar(50) not null) create table #log (taskID int not null, reportDate datetime not null, result varchar(50) not null, primary key(reportDate, taskId)) insert #task select 'Task 1' insert #task select 'Task 2' insert #task select 'Task 3' insert #task select 'Task 4' insert #task select 'Task 5' insert #task select 'Task 6' insert #log select taskID, 39951 + number, 'Result text...' from #task cross join ( select top 1000 row_number() over (order by a.id) as number from syscolumns a cross join syscolumns b cross join syscolumns c) n 

现在用一个执行计划运行这两个查询。

 select t.taskID, t.taskName, lg.reportDate, lg.result from #task t left join (select taskID, reportDate, result, rank() over (partition by taskID order by reportDate desc) rnk from #log) lg on lg.taskID = t.taskID and lg.rnk = 1 select t.taskID, t.taskName, lg.reportDate, lg.result from #task t outer apply ( select top 1 l.* from #log l where l.taskID = t.taskID order by reportDate desc) lg 

您可以看到外部应用查询更高效。 (不能附上计划,因为我是一个新用户……)