これは一般に解決するのが難しい問題ですが、オプティマイザーが計画を選択するのを支援するためにできることがいくつかあります。このスクリプトは、以下を説明するために、既知の擬似ランダムな行の分布で10,000行のテーブルを作成します。
CREATE TABLE dbo.SomeDateTable
(
Id INTEGER IDENTITY(1, 1) PRIMARY KEY NOT NULL,
StartDate DATETIME NOT NULL,
EndDate DATETIME NOT NULL
);
GO
SET STATISTICS XML OFF
SET NOCOUNT ON;
DECLARE
@i INTEGER = 1,
@s FLOAT = RAND(20120104),
@e FLOAT = RAND();
WHILE @i <= 10000
BEGIN
INSERT dbo.SomeDateTable
(
StartDate,
EndDate
)
VALUES
(
DATEADD(DAY, @s * 365, {d '2009-01-01'}),
DATEADD(DAY, @s * 365 + @e * 14, {d '2009-01-01'})
)
SELECT
@s = RAND(),
@e = RAND(),
@i += 1
END
最初の質問は、このテーブルにインデックスを付ける方法です。1つのオプションは、上の2つのインデックスを提供することでありDATETIME
、オプティマイザは、少なくとも上で追求するかどうかを選択することができるように、列のStartDate
かEndDate
。
CREATE INDEX nc1 ON dbo.SomeDateTable (StartDate, EndDate)
CREATE INDEX nc2 ON dbo.SomeDateTable (EndDate, StartDate)
当然、両方の不等式は、各インデックスの1つの列のみがクエリ例のシークをサポートできることStartDate
をEndDate
意味しますが、これは私たちができる最善の方法です。各インデックスの2番目の列をINCLUDE
キーではなく検討することもできますが、先頭の列で等値シークを実行し、2番目の列で不等値シークを実行できる他のクエリがある場合があります。また、この方法でより良い統計を得ることができます。とにかく...
DECLARE
@StartDateBegin DATETIME = {d '2009-08-01'},
@StartDateEnd DATETIME = {d '2009-10-15'},
@EndDateBegin DATETIME = {d '2009-08-05'},
@EndDateEnd DATETIME = {d '2009-10-22'}
SELECT
COUNT_BIG(*)
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
AND sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
このクエリは変数を使用するため、一般にオプティマイザーは選択性と分布を推測し、推測されたカーディナリティの推定値は81行になります。実際、クエリは2076行を生成しますが、これはより複雑な例では重要になる可能性があります。
SQL Server 2008 SP1 CU5以降(またはR2 RTM CU1)では、上記のクエリに追加するだけで、パラメーターの埋め込みの最適化を利用してより良い推定値を取得できます。これにより、バッチが実行される直前にコンパイルが行われ、SQL Serverは実際のパラメーター値を「参照」し、それらに対して最適化できます。この変更により、見積もりが468行に改善されます(ただし、これを確認するにはランタイム計画を確認する必要があります)。この推定値は81行よりも優れていますが、それでもすべて近いというわけではありません。トレースフラグ2301によって有効にされたモデリング拡張機能は、場合によっては役立ちますが、このクエリでは役立ちません。OPTION (RECOMPILE)
SELECT
問題は、2つの範囲検索で修飾された行が重複する場所です。オプティマイザーのコスト計算およびカーディナリティ推定コンポーネントで行われた単純化された仮定の1つは、述部が独立していることです(したがって、両方の選択性が50%の場合、両方を適用した結果は行の50%= 25%を修飾すると想定されます) )。この種の相関が問題となる場合、多くの場合、複数列および/またはフィルター処理された統計で回避できます。開始点と終了点が不明な2つの範囲では、これは実用的ではなくなります。これは、クエリをより良い推定値を生成するフォームに書き換えることに時々頼らなければならない場所です。
SELECT COUNT(*) FROM
(
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
INTERSECT
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
) AS intersected (id)
OPTION (RECOMPILE)
このフォームは、実際の2076行に対して2110行の実行時見積もりを生成します。TF 2301がオンになっていない限り、この場合、より高度なモデリング手法でトリックを確認し、以前とまったく同じ推定468行を生成します。
ある日、SQL Serverは間隔のネイティブサポートを取得する場合があります。統計サポートが優れている場合、開発者はこのようなクエリプランのチューニングを少し難しくするかもしれません。