Lee*_*ett 119 sql sql-server sql-server-2005 sql-server-2008
我一直在CROSS / OUTER APPLY和同事一起看,我们正在努力寻找现实生活中使用它们的例子.
我花了很多时间看看我何时应该使用Cross Apply而不是Inner Join?和谷歌搜索但主要(唯一)的例子似乎很奇怪(使用表中的行数来确定从另一个表中选择多少行).
我认为这种情况可能会受益于OUTER APPLY:
联系人表(每个联系人包含1条记录)通讯条目表(每个联系人可以包含n个电话,传真,电子邮件)
但是使用子查询,公共表表达式,OUTER JOIN与RANK()和OUTER APPLY似乎都同样执行.我猜这意味着该方案不适用于APPLY.
请分享一些现实生活中的例子并帮助解释这个功能!
Mar*_*ith 169
一些用途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
Run Code Online (Sandbox Code Playgroud)
2)为外部查询中的每一行调用表值函数
SELECT *
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle)
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
4) 取消隐藏多组列
假设违反表结构的1NF ....
CREATE TABLE T
(
Id INT PRIMARY KEY,
Foo1 INT, Foo2 INT, Foo3 INT,
Bar1 INT, Bar2 INT, Bar3 INT
);
Run Code Online (Sandbox Code Playgroud)
使用2008+ VALUES语法的示例.
SELECT Id,
Foo,
Bar
FROM T
CROSS APPLY (VALUES(Foo1, Bar1),
(Foo2, Bar2),
(Foo3, Bar3)) V(Foo, Bar);
Run Code Online (Sandbox Code Playgroud)
在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);
Run Code Online (Sandbox Code Playgroud)
Sar*_*avu 82
有各种情况你无法避免CROSS APPLY或OUTER APPLY.
考虑你有两张桌子.
主表
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 JOIN用CROSS APPLY.
1.如果我们想要在功能上加入2个TOP n结果表INNER JOIN
试想,如果我们需要选择Id并Name从Master和最后两个日期为每个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
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 APPLY即WHERE 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)
外面申请
1.如果我们想要在功能上加入2个TOP n结果表LEFT JOIN
考虑我们是否需要Master从Details表中选择Id和Name 以及每个Id的最后两个日期.
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
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 | NULL | NULL |
| 3 | C | NULL | NULL |
x------x---------x--------------x-------x
Run Code Online (Sandbox Code Playgroud)
这将带来错误的结果,即,即使我们加入,它也只会从Details表中带来最新的两个日期数据.所以正确的解决方案是使用.IdIdOUTER 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
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 |
| 3 | C | NULL | NULL |
x------x---------x--------------x-------x
Run Code Online (Sandbox Code Playgroud)
2.当我们需要LEFT JOIN使用功能时functions.
OUTER APPLYLEFT JOIN当我们需要从Master表和a 获得结果时,可以用作替代function.
SELECT M.ID,M.NAME,C.PERIOD,C.QTY
FROM MASTER M
OUTER 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 |
| 3 | C | NULL | NULL |
x------x---------x--------------x-------x
Run Code Online (Sandbox Code Playgroud)
的共同特点
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
Run Code Online (Sandbox Code Playgroud)
当您使用AND 将一个列UNPIVOT引入时,它将默认消除值.FROMDATETODATENULL
SELECT ID,DATES
FROM MYTABLE
UNPIVOT (DATES FOR COLS IN (FROMDATE,TODATE)) P
Run Code Online (Sandbox Code Playgroud)
产生以下结果.请注意,我们已经错过了Id数字记录3
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
Run Code Online (Sandbox Code Playgroud)
在这种情况下,CROSS APPLY或OUTER APPLY将是有用的
SELECT DISTINCT ID,DATES
FROM MYTABLE
OUTER APPLY(VALUES (FROMDATE),(TODATE))
COLUMNNAMES(DATES)
Run Code Online (Sandbox Code Playgroud)
它形成以下结果并保留Id其价值所在3
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)
一个真实的例子是,如果您有一个调度程序,并希望查看每个计划任务的最新日志条目.
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
Run Code Online (Sandbox Code Playgroud)
要回答以上几点,请举一个例子:
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
Run Code Online (Sandbox Code Playgroud)
现在,使用执行计划运行两个查询。
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
Run Code Online (Sandbox Code Playgroud)
您可以看到外部应用查询更有效。(由于我是新用户,所以无法附上该计划... Doh。)