首页 文章

现实生活中的例子,何时在SQL中使用OUTER / CROSS APPLY

提问于
浏览
98

我一直在和一位同事一起看 CROSS / OUTER APPLY ,我们正在努力寻找真实生活中使用它们的例子 .

我花了很多时间看When should I use Cross Apply over Inner Join?和Google搜索,但主要(唯一)的例子看起来很奇怪(使用表中的rowcount来确定从另一个表中选择多少行) .

我认为这种情况可能会受益于 OUTER APPLY

联系人表(每个联系人包含1条记录)通讯条目表(每个联系人可以包含n个电话,传真,电子邮件)

但是使用子查询,公共表表达式 OUTER JOINRANK()OUTER APPLY 似乎都表现相同 . 我'm guessing this means the scenario isn'适用于 APPLY .

请分享一些现实生活中的例子并帮助解释这个功能!

4 回答

  • 7

    在各种情况下,您无法避免 CROSS APPLYOUTER APPLY .

    考虑你有两张 table .

    MASTER TABLE

    x------x--------------------x
    | Id   |        Name        |
    x------x--------------------x
    |  1   |          A         |
    |  2   |          B         |
    |  3   |          C         |
    x------x--------------------x
    

    DETAILS TABLE

    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 替换 INNER JOIN .

    1. If we want to join 2 tables on TOP n results with INNER JOIN functionality

    考虑是否需要从 Master 中选择 IdName ,并从 Details table 中为每个 Id 选择最后两个日期 .

    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
    

    看,它生成最后两个日期的结果,最后两个日期是 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
    

    并形成他的结果 .

    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 不能执行此操作(抛出编译错误) . 找到最后两个日期时,加入在 CROSS APPLY 内完成,即 WHERE M.ID=D.ID .

    2. When we need INNER JOIN functionality using functions.

    当我们需要从 Master 表和 function 获得结果时, CROSS APPLY 可以用作 INNER JOIN 的替代 .

    SELECT M.ID,M.NAME,C.PERIOD,C.QTY
    FROM MASTER M
    CROSS 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   |
    x------x---------x--------------x-------x
    

    外部申请

    1. If we want to join 2 tables on TOP n results with LEFT JOIN functionality

    考虑我们是否需要从 Master 中选择Id和Name,并从 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
    

    这将带来错误的结果,即,即使我们加入 Id ,它也只会带来 Details 表中最新的两个日期数据,而不管 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
    

    形成以下所需结果

    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. When we need LEFT JOIN functionality using 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
    

    CROSS APPLY和OUTER APPLY的共同特征

    CROSS APPLYOUTER 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
    

    当您使用 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
    

    在这种情况下, CROSS 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
    
  • 4

    一个真实的例子是,如果您有一个调度程序,并希望查看每个计划任务的最新日志条目 .

    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
    
  • 69

    要回答上述问题,请举例说明:

    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
    

    您可以看到外部应用查询更有效 . (无法附上计划,因为我是新用户... Doh . )

  • 144

    APPLY 的一些用途是......

    1) Top N per group queries(某些基数可能更有效)

    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) Reusing a column alias

    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) Unpivoting more than one group of columns

    假设违反表结构的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);
    

相关问题