私は同僚と一緒にCROSS / OUTER APPLY
を見ていましたが、実際の使用例を見つけるのに苦労しています。
Inner JoinでCross Applyを使用する必要がある場合 とグーグルを見るのにかなりの時間を費やしましたが、メインの(唯一の)例はかなり奇妙に見えます(テーブルの行数を使用して、別のテーブルから選択する多くの行)。
私はこのシナリオがOUTER APPLY
の恩恵を受けると思った:
連絡先テーブル(連絡先ごとに1つのレコードが含まれます)
しかし、サブクエリ、共通テーブル式、OUTER JOIN
をRANK()
およびOUTER APPLY
とともに使用すると、すべて同じように実行されるようです。これは、シナリオがAPPLY
に適用できないことを意味していると思います。
いくつかの実際の例を共有し、機能の説明を手伝ってください!
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)
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)
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);
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 JOIN
をCROSS 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 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。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 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 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
実際の例としては、スケジューラがあり、スケジュールされた各タスクの最新のログエントリを確認したい場合があります。
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
上記の点に答えるために例を挙げます:
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。)