日付範囲を取得する最も効率的な方法


16

このようなテーブル構造で日付範囲を取得する最も効率的な方法は何ですか?

create table SomeDateTable
(
    id int identity(1, 1) not null,
    StartDate datetime not null,
    EndDate datetime not null
)
go

との両方の範囲が必要だStartDateとしEndDateます。換言すれば、場合StartDateの間に落ちる@StartDateBegin@StartDateEnd、とEndDateの間に落ちる@EndDateBegin@EndDateEnd、その後、何かをします。

これについておそらくいくつかの方法があることは知っていますが、最も推奨されるのは何ですか?

回答:


29

これは一般に解決するのが難しい問題ですが、オプティマイザーが計画を選択するのを支援するためにできることがいくつかあります。このスクリプトは、以下を説明するために、既知の擬似ランダムな行の分布で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、オプティマイザは、少なくとも上で追求するかどうかを選択することができるように、列のStartDateEndDate

CREATE INDEX nc1 ON dbo.SomeDateTable (StartDate, EndDate)
CREATE INDEX nc2 ON dbo.SomeDateTable (EndDate, StartDate)

当然、両方の不等式は、各インデックスの1つの列のみがクエリ例のシークをサポートできることStartDateEndDate意味しますが、これは私たちができる最善の方法です。各インデックスの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は間隔のネイティブサポートを取得する場合があります。統計サポートが優れている場合、開発者はこのようなクエリプランのチューニングを少し難しくするかもしれません。


5

すべてのデータ配布に高速なソリューションはわかりませんが、すべての範囲が短い場合、通常はスピードアップできます。たとえば、このクエリではなく、範囲が1日より短い場合:

SELECT  TaskId ,    
        TaskDescription ,
        StartedAt ,    
        FinishedAt    
FROM    dbo.Tasks    
WHERE   '20101203' BETWEEN StartedAt AND FinishedAt

もう1つの条件を追加できます。

SELECT  TaskId ,    
        TaskDescription ,
        StartedAt ,    
        FinishedAt    
FROM    dbo.Tasks    
WHERE   '20101203' BETWEEN StartedAt AND FinishedAt
    AND StartedAt >= '20101202'
    AND FinishedAt <= '20101204' ;

その結果、テーブル全体をスキャンする代わりに、クエリは2日間の範囲のみをスキャンします。これは高速です。範囲が長くなる可能性がある場合は、短い範囲のシーケンスとして保存できます。詳細はこちら:制約の助けを借りたSQLクエリのチューニング

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.