真实的例子,在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 APPLY
replaceINNER JOIN
。
1.如果我们想要使用INNER JOIN
function在TOP n
结果中join2个表格
考虑是否需要从Master
Details table
selectId
和Name
,并从Details table
为每个Id
select最后两个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 JOIN
function使用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 JOIN
function在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
您可以看到外部应用查询更高效。 (不能附上计划,因为我是一个新用户……)