非正規化されたデータベースに防犯カメラの映像のデータベースがあります。複数の画像を撮影する複数のカメラがある場所があります。
場所+カメラ+画像capture_dateは、クラスター化された主キーであり、現在、テーブル上の唯一のインデックスです。キッカーが1台のカメラを検索している場合、SSMSから1ミリ秒未満、Webアプリケーションから約70ミリ秒かかります。私の現在のCTEソリューションは、台のカメラで約3分かかります。
ある場所にあるカメラの概要を説明するには、特定の日付(現在の日付など)に最も近い各カメラから2つの画像を選択する必要があります。このため、絶対値が必要です(検索日の前後の日付も同様に有効です)。したがって、最小のABS(@date -capture_date)
で検索しています。
これが現在のコードです。動作しますが、SARGableではなく、非常に低速です。また、パーティションごとに数十万の画像が存在する可能性があるため、CTEのカメラごとに上位2行のみが必要です。
DECLARE @date datetime,
@location varchar(4)
SET @date ='2011-12-13 12:00:00'
SET @location='CS01';
WITH CTE AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY Camera ORDER BY abs(datediff(second,@date, [capture_date]))) AS Ranking
FROM rs_camera_pictures
WHERE
location=@location)
SELECT * FROM CTE WHERE Ranking <= 2
これは単なる出発点であり、調整が必要になる可能性があります。
基本的に、これにより、指定した日付に最も近い4つのキャプチャ日付が取得されます(2つは後、2つは前)。
使用するものを選択するために、外側の選択にいくつかのロジックを追加する必要がありますが、すべてではなく4つのフィールドでDATEDIFF
を実行します。
WITH CTE AS
(
SELECT camera, MIN(r1.capture_date) as 'After', MAX(r2.capture_date) as 'Before'
FROM rs_camera_pictures r1
INNER JOIN rs_camera_pictures r2
ON r1.location = r2.location
AND r1.camera = r2.camera
WHERE r1.capture_date > @date
AND r2.capture_date < @date
GROUP BY camera
UNION ALL
SELECT camera, MIN(r1.capture_date) as 'After', MAX(r2.capture_date) as 'Before'
FROM rs_camera_pictures r1
INNER JOIN rs_camera_pictures r2
ON r1.location = r2.location
AND r1.camera = r2.camera
INNER JOIN CTE c
ON r1.location = c.location
AND r1.camera = c.camera
WHERE r1.capture_date > c.[After]
AND r2.capture_date < c.Before
GROUP BY camera
)
これにより、選択した場所のカメラごとに4行が返され、それらをランク付けして、各カメラに最適な2行が返されます。
LocationCameraは、場所のカメラの個別のリストです。
DECLARE @date datetime,
@location varchar(4)
SET @date ='2011-12-13 12:00:00'
SET @location='CS01';
SELECT Location,Camera,Capture_Date,Ranking
FROM (
SELECT Location,Camera,Capture_Date,
ROW_NUMBER() OVER (PARTITION BY Camera ORDER BY abs(datediff(second,@date, [capture_date]))) AS Ranking
FROM (
SELECT L.Location,
L.Camera,
CP.capture_date
FROM LocationCamera L
CROSS APPLY (
SELECT TOP (2)
CP.[capture_date]
FROM rs_camera_pictures CP
WHERE L.Location = CP.Location
AND L.Camera = CP.Camera
AND CP.capture_date >= @date
ORDER BY [capture_date]
) TopN
WHERE L.Location = @Location
UNION ALL
SELECT L.Location,
L.Camera,
CP.[capture_date]
FROM LocationCamera L
CROSS APPLY (
SELECT TOP (2)
CP.[capture_date]
FROM rs_camera_pictures CP
WHERE L.Location = CP.Location
AND L.Camera = CP.Camera
AND CP.capture_date < @date
ORDER BY [capture_date] DESC
) BottomN
WHERE L.Location = @location
) D
) R
WHERE Ranking <= 2
私の知る限り(さらに、私がまだ知らなかった方法があるかどうかを確認するためのGoogleの検索とハッキング)、非SARGable ABS(DATEDIFFを回避し、現在のパフォーマンスを上回る解決策はありません。
したがって、SARG不可能な検索を実装する必要がある場合、最も重要なことは、セットのサイズを制限するSARG可能な方法を見つけることです。現在、WHERE句にはSARGable句が含まれていますが、カメラごとに数十万行になる可能性がある場合(つまり、場所ごとに数百万行になる可能性がある場合)、問題のドメインをさらに縮小することは理にかなっています。
最大検索距離に適切な値を選択し、日付の上限と下限を設定することをお勧めします。例えばカメラに障害が発生していない限り、システムは通常、常にleast 5分ごとに写真を撮るとします。 6分の上限と下限を設定して(タイミングの「ジッター」を考慮して)、それらの範囲内の画像のみをランク付けできます。これは次のようになります。
DECLARE @date datetime,
@location varchar(4) ,
@dateUB DATETIME,
@dateLB DATETIME
SET @date ='2011-12-13 12:00:00'
SET @DateUB = DATEADD(minute, 6, @matchDate)
SET @DateLB = DATEADD(minute, -6, @matchDate)
SET @location='CS01';
WITH CTE AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY Camera ORDER BY abs(datediff(second,@date, [capture_date]))) AS Ranking
FROM rs_camera_pictures
WHERE
location=@location
AND capture_date BETWEEN @dateUB AND @dateLB )
SELECT * FROM CTE WHERE Ranking <= 2
Capture_dateとlocationがインデックスでカバーされている場合、これにより大幅な改善が得られるはずです。
たぶん、選択した日付から両方向にトップ2を取り、次にその日からトップ2を取ります。
DECLARE @date datetime,
@location varchar(4)
SET @date ='2011-12-13 12:00:00'
SET @location='CS01';
WITH candidates AS (
SELECT * FROM (
SELECT TOP 2 *
FROM rs_camera_pictures
WHERE location=@location
AND capture_date <= @date
ORDER BY capture_date DESC
) [before]
UNION
SELECT * FROM (
SELECT TOP 2 *
FROM rs_camera_pictures
WHERE location=@location
AND capture_date >= @date
ORDER BY capture_date ASC
) [after]
),
CTE AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY Camera ORDER BY abs(datediff(second,@date, [capture_date]))) AS Ranking
FROM candidates
WHERE
location=@location
)
SELECT * FROM CTE WHERE Ranking <= 2