web-dev-qa-db-ja.com

実際の例、SQLでOUTER / CROSS APPLYを使用する場合

私は同僚と一緒にCROSS / OUTER APPLYを見ていましたが、実際の使用例を見つけるのに苦労しています。

Inner JoinでCross Applyを使用する必要がある場合 とグーグルを見るのにかなりの時間を費やしましたが、メインの(唯一の)例はかなり奇妙に見えます(テーブルの行数を使用して、別のテーブルから選択する多くの行)。

私はこのシナリオがOUTER APPLYの恩恵を受けると思った:

連絡先テーブル(連絡先ごとに1つのレコードが含まれます)

しかし、サブクエリ、共通テーブル式、OUTER JOINRANK()およびOUTER APPLYとともに使用すると、すべて同じように実行されるようです。これは、シナリオがAPPLYに適用できないことを意味していると思います。

いくつかの実際の例を共有し、機能の説明を手伝ってください!

114
Lee Tickett

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);
160
Martin Smith

CROSS APPLYまたはOUTER APPLYを避けることができないさまざまな状況があります。

2つのテーブルがあるとします。

マスターテーブル

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                                       



クロス適用

INNER JOINCROSS APPLYに置き換える必要がある多くの状況があります。

1。TOP nの結果で2つのテーブルをINNER JOIN機能で結合したい場合

IdからNameおよびMasterを選択し、Details tableから各Idの最後の2つの日付を選択する必要があるかどうかを検討します。

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

参照してください、最後の2つの日付のIdで最後の2つの日付の結果を生成し、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がこれを実行できない外部テーブルを参照できます(コンパイルエラーがスローされます)。最後の2つの日付を見つけると、CROSS APPLY内で結合が行われます。つまり、WHERE M.ID=D.IDです。

2。関数を使用してINNER JOIN機能が必要な場合。

Masterテーブルとfunctionから結果を取得する必要がある場合は、CROSS APPLYINNER 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。TOP nの結果で2つのテーブルをLEFT JOIN機能で結合したい場合

MasterからIdとNameを選択し、Detailsテーブルから各Idの最後の2つの日付を選択する必要があるかどうかを検討します。

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テーブルから最新の2つの日付データのみを取得します。したがって、適切な解決策は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機能が必要な場合

Masterテーブルとfunctionから結果を取得する必要がある場合は、OUTER APPLYLEFT 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 APPLYOUTER 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を1つの列に追加すると、デフォルトでNULLの値が削除されます。

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

以下の結果が生成されます。 Id number 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 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
79
Sarath Avanavu

実際の例としては、スケジューラがあり、スケジュールされた各タスクの最新のログエントリを確認したい場合があります。

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
8
BJury

上記の点に答えるために例を挙げます:

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

そして、実行プランを使用して2つのクエリを実行します。

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。)

5
BJury