【问题标题】:Where to use Outer Apply在哪里使用外部应用
【发布时间】:2015-03-06 10:40:37
【问题描述】:

主表

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 JOINOUTER APPLY 时,我得到了相同的结果。

LEFT JOIN

SELECT T1.ID,T1.NAME,T2.PERIOD,T2.QTY 
FROM MASTER T1
LEFT JOIN DETAILS T2 ON T1.ID=T2.ID

OUTER APPLY

SELECT T1.ID,T1.NAME,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

【问题讨论】:

标签: sql-server sql-server-2008 left-join outer-apply


【解决方案1】:

在以下情况下,LEFT JOIN 应替换为 OUTER APPLY

1.如果我们想根据TOP n 结果连接两个表

考虑我们是否需要从Master 中选择IdName,并从Details 表中选择每个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

形成以下结果

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

这将带来错误的结果,即它只会从Details 表中带来最新的两个日期数据,而不考虑Id,即使我们加入Id。所以正确的解决方案是使用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

这是工作:在LEFT JOIN 中,TOP 2 日期将仅在派生表D 中执行查询后加入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

2。当我们需要LEFT JOIN 功能时使用functions

当我们需要从Master 表和function 中获取结果时,OUTER APPLY 可以用作LEFT JOIN 的替代品。

SELECT M.ID,M.NAME,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,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

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

当您使用UNPIVOTFROMDATE AND TODATE 带到一列时,默认情况下会消除NULL 值。

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

生成以下结果。注意我们漏掉了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

在这种情况下,可以使用APPLYCROSS APPLYOUTER APPLY,可以互换)。

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

形成以下结果并保留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

【讨论】:

  • 由同一用户根据其他引用问题中的答案提问和回答? stackoverflow.com/questions/1139160/…
  • “OUTER APPLY 可用于在取消透视时保留 NULL 值”——在 comment to your other answer 中,我已经指出,如果您想使用哪个 APPLY 进行取消透视,则使用它并不重要,但您从未在回复中解决该问题。这个答案意味着(以及其他明确的状态)CROSS APPLY 在这种情况下不会保留 NULL,which is wrong。在分享知识之前,您应该检查您的事实。
  • 问题是关于OUTER APPLY 而不是CROSS APPLY。我没有提到CROSS APPLY,因为我是来回答我应该在哪里使用外部应用。在这种情况下,还有其他方法,例如使用UNION ALL 而不是UNPIVOT。我应该将它们全部添加到答案中吗?那有意义吗? @Andriy M
  • CROSS APPLY 和 OUTER APPLY 可以互换的地方,我认为应该提到这一点,因为否则人们(尤其是新学习者)可能会认为在这些情况下他们只能使用 OUTER APPLY 而不能使用 CROSS APPLY。至于特别是 unpivoting,这就是您自己错误地认为两个 APPLY 在那里的行为不同的地方。您的其他答案仍然指出 CROSS APPLY 不保留 NULL,而此答案说 OUTER APPLY 保留。因此,首先,修复该差异,其次,考虑提及两个 APPLY 都可以在这种情况下使用,以免产生疑问。
  • 我没有提到CROSS APPLY,因为问题是关于OUTER APPLY。好的。如果您认为用户可能对CROSS APPLYOUTER APPLY 感到困惑,我会在答案中更新。 @Andriy M
【解决方案2】:

在您的示例查询中,结果确实是一样的。

但是OUTER APPLY 可以做更多:对于每个外部行,您可以生成任意内部结果集。例如,您可以加入TOP 1 ORDER BY ... 行。 LEFT JOIN 不能这样做。

内部结果集的计算可以引用外部列(就像您的示例一样)。

OUTER APPLY 严格来说比LEFT JOIN 更强大。这很容易看出,因为每个LEFT JOIN 都可以像您一样重写为OUTER APPLY。不过,它的语法更冗长。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-12-15
    • 2011-09-17
    • 1970-01-01
    • 1970-01-01
    • 2016-10-06
    • 2014-02-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多