主表
- 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
当使用LEFT JOIN和OUTER APPLY时,我得到相同的结果.
LEFT JOIN
- SELECT T1.ID,T1.NAME,T2.PERIOD,T2.QTY
- FROM MASTER T1
- LEFT JOIN DETAILS T2 ON T1.ID=T2.ID
外部申请
- SELECT T1.ID,TAB.PERIOD,TAB.QTY
- FROM MASTER T1
- OUTER APPLY
- (
- SELECT ID,PERIOD,QTY
- FROM DETAILS T2
- WHERE T1.ID=T2.ID
- )TAB
我应该在哪里使用LEFT JOIN以及我应该在哪里使用OUTER APPLY
解决方法
在以下情况下,应使用OUTER APPLY替换LEFT JOIN.
1.如果我们想根据TOP n结果加入两个表
考虑我们是否需要从Master中选择Id和Name,并从Details表中为每个Id选择最后两个日期.
- SELECT M.ID,M.NAME,D.PERIOD,D.QTY
- FROM MASTER M
- LEFT JOIN
- (
- SELECT TOP 2 ID,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
这将带来错误的结果,即,即使我们加入Id,它也只会从详细信息表中带来最新的两个日期数据而不管Id.所以正确的解决方案是使用OUTER APPLY.
- SELECT M.ID,D.QTY
- FROM MASTER M
- OUTER APPLY
- (
- SELECT TOP 2 ID,QTY
- FROM DETAILS D
- WHERE M.ID=D.ID
- ORDER BY CAST(PERIOD AS DATE)DESC
- )D
这是工作:在LEFT JOIN中,只有在派生表D中执行查询后,才会将TOP 2日期加入MASTER.在OUTER APPLY中,它使用在OUTER APPLY中加入WHERE M.ID = D.ID,这样Master中的每个ID都将与TOP 2日期相结合,这将带来以下结果.
- 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
当我们需要从主表和函数中获取结果时,OUTER APPLY可以用作LEFT JOIN的替换.
- SELECT M.ID,C.PERIOD,C.QTY
- FROM MASTER M
- OUTER APPLY dbo.FnGetQty(M.ID) C
功能就在这里.
- CREATE FUNCTION FnGetQty
- (
- @Id INT
- )
- RETURNS TABLE
- AS
- RETURN
- (
- SELECT ID,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
3.在取消隐藏时保留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和TODATE带到一列时,它将默认消除NULL值.
- SELECT ID,DATES
- FROM MYTABLE
- UNPIVOT (DATES FOR COLS IN (FROMDATE,TODATE)) P
产生以下结果.请注意,我们错过了身份证号码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
在这种情况下,可以使用APPLY(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