真实的例子,在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 APPLY或OUTER 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 tableselectId和Name ,并从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 APPLY和OUTER APPLY共同特点
  CROSS APPLY或OUTER 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 
 当您使用UNPIVOT将FROMDATE 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 APPLY或OUTER 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 
您可以看到外部应用查询更高效。 (不能附上计划,因为我是一个新用户……)