TLDRバージョン
このクエリに役立つインデックスが明らかに欠落しています。欠落しているインデックスを追加すると、それ自体で桁違いの改善が生じる可能性があります。
SQL Server 2012を使用している場合は、を使用してクエリを書き直すLEAD
こともできます(ただし、インデックスがないことによるメリットもあります)。
まだ2005/2008を使用している場合は、既存のクエリにいくつかの改善を加えることができますが、インデックスの変更に比べて影響は比較的小さくなります。
長いバージョン
これに3分かかるには、有用なインデックスがまったくなく、最大のメリットはインデックスを追加することだと思います(月に一度実行されるレポートの場合、3つの列から適切にインデックスが付けられた#temp
テーブルにデータをコピーするだけで十分な場合があります)永続的なインデックスを作成したくない場合)。
わかりやすくするためにテーブルを簡略化し、40K行あると言います。以下のテストデータを想定
CREATE TABLE TestDuration
(
Id UNIQUEIDENTIFIER DEFAULT NEWID() PRIMARY KEY,
VALIDATION_TIMESTAMP DATETIME,
ID_TICKET BIGINT,
ID_PLACE BIGINT,
OtherColumns CHAR(100) NULL
)
INSERT INTO TestDuration
(VALIDATION_TIMESTAMP,
ID_TICKET,
ID_PLACE)
SELECT TOP 40000 DATEADD(minute, ROW_NUMBER() OVER (ORDER BY (SELECT 0)), GETDATE()),
ABS(CHECKSUM(NEWID())) % 10,
ABS(CHECKSUM(NEWID())) % 100
FROM master..spt_values v1,
master..spt_values v2
元のクエリはMAXDOP 1
、次のIO統計で私のマシンで51秒かかります
Table 'Worktable'. Scan count 79990, logical reads 1167573, physical reads 0
Table 'TestDuration'. Scan count 3, logical reads 2472, physical reads 0.
テーブル内の40,000行のそれぞれについてID_TICKET
、次の行を順番に識別するために、一致するすべての行を2種類実行しています。VALIDATION_TIMESTAMP
以下のようにインデックスを追加するだけで、経過時間は406ミリ秒に短縮され、100倍以上向上します(この回答の後続のクエリは、このインデックスが適切に配置されていることを前提としています)。
CREATE NONCLUSTERED INDEX IX
ON TestDuration(ID_TICKET, VALIDATION_TIMESTAMP)
INCLUDE (ID_PLACE)
計画は次のようになり、80,000のソートとスプール操作がインデックスシークに置き換えられました。
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0
Table 'TestDuration'. Scan count 79991, logical reads 255707, physical reads 0
ただし、すべての行に対して2回のシークを実行しています。で書き換えるとCROSS APPLY
、これらを組み合わせることができます。
SELECT VisitDurationCalcTable.ID_PLACE AS ID_PLACE_IN,
VisitDurationCalcTable.ID_NEXT_VISIT_PLACE AS ID_PLACE_OUT,
COUNT(visitduration) AS NUMBER_OF_VISITS,
AVG(visitduration) AS AVERAGE_VISIT_DURATION
FROM (SELECT EntryData.VALIDATION_TIMESTAMP,
EntryData.ID_TICKET,
EntryData.ID_PLACE,
CA.ID_PLACE AS ID_NEXT_VISIT_PLACE,
DATEDIFF(n, EntryData.VALIDATION_TIMESTAMP, CA.VALIDATION_TIMESTAMP) AS visitduration
FROM TestDuration EntryData
CROSS APPLY (SELECT TOP 1 ID_PLACE,
VALIDATION_TIMESTAMP
FROM TestDuration
WHERE ID_TICKET = EntryData.ID_TICKET
AND VALIDATION_TIMESTAMP > EntryData.VALIDATION_TIMESTAMP
ORDER BY VALIDATION_TIMESTAMP ASC) CA) AS VisitDurationCalcTable
GROUP BY VisitDurationCalcTable.ID_PLACE,
VisitDurationCalcTable.ID_NEXT_VISIT_PLACE
これにより、経過時間は269ミリ秒になります
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0
Table 'TestDuration'. Scan count 40001, logical reads 127988, physical reads 0
読み取りの数はまだかなり多いですが、シークはスキャンによって読み取られたばかりのすべての読み取りページであるため、すべてキャッシュ内のページです。テーブル変数を使用すると、読み取り回数を減らすことができます。
DECLARE @T TABLE (
VALIDATION_TIMESTAMP DATETIME,
ID_TICKET BIGINT,
ID_PLACE BIGINT,
RN INT
PRIMARY KEY(ID_TICKET, RN) )
INSERT INTO @T
SELECT VALIDATION_TIMESTAMP,
ID_TICKET,
ID_PLACE,
ROW_NUMBER() OVER (PARTITION BY ID_TICKET ORDER BY VALIDATION_TIMESTAMP) AS RN
FROM TestDuration
SELECT T1.ID_PLACE AS ID_PLACE_IN,
T2.ID_PLACE AS ID_PLACE_OUT,
COUNT(*) AS NUMBER_OF_VISITS,
AVG(DATEDIFF(n, T1.VALIDATION_TIMESTAMP, T2.VALIDATION_TIMESTAMP)) AS AVERAGE_VISIT_DURATION
FROM @T T1
INNER MERGE JOIN @T T2
ON T1.ID_TICKET = T2.ID_TICKET
AND T2.RN = T1.RN + 1
GROUP BY T1.ID_PLACE,
T2.ID_PLACE
ただし、少なくとも私にとっては、経過時間が301ミリ秒(挿入の場合は43ミリ秒、選択の場合は258ミリ秒)にわずかに増加しましたが、永続的なインデックスを作成する代わりに、これは依然として適切なオプションである可能性があります。
(Insert)
Table 'TestDuration'. Scan count 1, logical reads 233, physical reads 0
(Select)
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0
Table '#0C50D423'. Scan count 2, logical reads 372, physical reads 0
最後に、SQL Server 2012を使用している場合は、LEAD
(SQL Fiddle)を使用できます。
WITH CTE
AS (SELECT ID_PLACE AS ID_PLACE_IN,
LEAD(ID_PLACE) OVER (PARTITION BY ID_TICKET
ORDER BY VALIDATION_TIMESTAMP) AS ID_PLACE_OUT,
DATEDIFF(n,
VALIDATION_TIMESTAMP,
LEAD(VALIDATION_TIMESTAMP) OVER (PARTITION BY ID_TICKET
ORDER BY VALIDATION_TIMESTAMP)) AS VISIT_DURATION
FROM TestDuration)
SELECT ID_PLACE_IN,
ID_PLACE_OUT,
COUNT(*) AS NUMBER_OF_VISITS,
AVG(VISIT_DURATION) AS AVERAGE_VISIT_DURATION
FROM CTE
WHERE ID_PLACE_OUT IS NOT NULL
GROUP BY ID_PLACE_IN,
ID_PLACE_OUT
それは私に249ミリ秒の経過時間を与えました
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0
Table 'TestDuration'. Scan count 1, logical reads 233, physical reads 0
このLEAD
バージョンは、インデックスがなくても良好に機能します。最適なインデックスを省略すると、プランに追加が追加SORT
され、テストテーブルのより広いクラスター化インデックスを読み取る必要がありますが、それでも293ミリ秒の経過時間で完了します。
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0
Table 'TestDuration'. Scan count 1, logical reads 824, physical reads 0