フィルタリングされたインデックスは、フィルタリングされたパーツがWHEREではなくJOINにある場合にのみ使用されます


10

以下のフィルターされたインデックスを作成しましたが、2つのクエリをさらに実行すると、このインデックスは、where句ではなく、JOINにEND_DTTMがある最初の例のシークにのみ使用されます(クエリの唯一の違いです)。 。なぜこれが起こるのか誰かが説明できますか?

インデックスの作成

CREATE NONCLUSTERED INDEX [ix_PATIENT_LIST_BESPOKE_LIST_ID_includes] ON [dbo].[PATIENT_LIST_BESPOKE] 
(
    [LIST_ID] ASC,
    [END_DTTM] ASC
)
WHERE ([END_DTTM] IS NULL)

クエリ

DECLARE @LIST_ID INT = 3655

--This one seeks on the index

SELECT  
    PATIENT_LISTS.LIST_ID
FROM    
    DBO.PATIENT_LISTS
    LEFT JOIN DBO.PATIENT_LIST_BESPOKE ON PATIENT_LISTS.LIST_ID = PATIENT_LIST_BESPOKE.LIST_ID  
                                      AND PATIENT_LIST_BESPOKE.END_DTTM IS NULL
WHERE
    PATIENT_LISTS.LIST_ID = @LIST_ID

--This one scans on the index

SELECT  
    PATIENT_LISTS.LIST_ID
FROM    
    DBO.PATIENT_LISTS
    LEFT JOIN DBO.PATIENT_LIST_BESPOKE ON PATIENT_LISTS.LIST_ID = PATIENT_LIST_BESPOKE.LIST_ID  
WHERE   
    PATIENT_LISTS.LIST_ID = @LIST_ID AND
    PATIENT_LIST_BESPOKE.END_DTTM IS NULL   

回答:


12

オプティマイザーが述部を(フィルターされた、またはそれ以外の)インデックスに一致させるには、述部が論理クエリツリーのGet操作に隣接している必要があります。これを容易にするために、述部は一般に、最適化が始まる前に、論理ツリーのリーフのできるだけ近くにプッシュされます。

大幅に簡略化するために、物理インデックス戦略の実装はこれを行います:

Predicate + Logical Get -> Physical Get (using Index)

対象のクエリは、外部結合の上にある述語で始まります。

Predicate on T2 --+-- LOJ -- Get (T1)
                       |
                       +---- Get (T2)

述語がGetに隣接していないため、この形状はインデックス戦略ルールと一致しません。したがって、答えの最初の部分は、述語を外部結合を超えてプッシュできない限り、フィルター選択されたインデックスの照合が失敗することです。

2番目の部分は、変換がほとんど有効ではないため、オプティマイザが、保持された側の外部結合を通過して述語を移動するために必要な探索ルールを含まないことです。オプティマイザの一般的な機能として、最も頻繁に使用されるルールのみが実装されています。

その結果、この場合、フィルターされたインデックスの照合は失敗します。明確にするために、書き換えは、引用する非常に特殊なケース(2番目のクエリ)で有効です。

最初のクエリフォーム(セマンティクスが異なる)の場合、述語は最初から結合に関連付けられており、述語プッシュダウンロジックこれをGetまで短い距離で移動できます。上記で説明した。

背景と詳細情報:


9

1つは結合の前にフィルタリングでき、もう1つは後でフィルタリングできるため、これらは意味的に同じクエリではありません。簡単な例で説明します。

CREATE TABLE dbo.Lefty(LeftyID INT PRIMARY KEY);

CREATE TABLE dbo.Righty(LeftyID INT, SomeList INT);

INSERT dbo.Lefty(LeftyID) VALUES(1),(2),(3);

INSERT dbo.Righty(LeftyID, SomeList) VALUES(1,1),(1,NULL),(2,2);

クエリ1は3つの行すべてを返します。

SELECT l.LeftyID, r.SomeList
FROM dbo.Lefty AS l
LEFT OUTER JOIN dbo.Righty AS r
ON l.LeftyID = r.LeftyID
AND r.SomeList IS NULL;

ただし、クエリ2では、LeftyID 2が省略されています。

SELECT l.LeftyID, r.SomeList
FROM dbo.Lefty AS l
LEFT OUTER JOIN dbo.Righty AS r
ON l.LeftyID = r.LeftyID
WHERE r.SomeList IS NULL;

SQLfiddleの証明

アンチセミ結合を実行しようとしている場合、テストされる列はnull可能ではない必要があります。ONとWHEREの間で基準を移動しても、INNER結合のみを処理する場合は論理的な違いはありませんが、OUTERの場合は大きな違いがあります。また、フィルター処理されたインデックスを使用できるかどうかよりも、結果が正しいことに注意する必要があります。


答えに感謝しますが、クエリが同じであるとは主張していません。なぜ、1つのクエリがフィルターされたインデックスを使用し、他のクエリが使用しないのかを尋ねています。
クリス

@chrisインデックスヒントでそのインデックスを強制しようとしましたか?実際の実行後の計画を、そのヒントの有無で比較したいと思います。私には、オプティマイザが反セミ結合を実行していると信じているときにそのインデックスを無視していることは明らかです(その場合、null許容列が使用されるとは想定されていないため)、それが正しいかどうかはわかりませんコストや操作の順序、または左側からの行がフィルター選択されたインデックスにある行よりもはるかに多い可能性があるという基本的な知識で行います。計画を見ることは役立つかもしれません。
アーロンバートランド

3

2つのクエリは、意味と結果が異なります。以下は書き換えなので、2つのクエリが何を行っているかがより明確になります。

-- 1st query
SELECT  
    a.LIST_ID
FROM    
      ( SELECT LIST_ID 
        FROM   DBO.PATIENT_LISTS
        WHERE  LIST_ID = @LIST_ID
      ) AS a
    LEFT JOIN  
      ( SELECT LIST_ID                    -- the filtered index
        FROM   DBO.PATIENT_LIST_BESPOKE   -- can be used
        WHERE  END_DTTM IS NULL           -- for the subquery
      ) AS b
    ON  a.LIST_ID = b.LIST_ID ;           -- and the join

そして2番目:

-- 2nd query
SELECT  
    a.LIST_ID
FROM    
      ( SELECT LIST_ID 
        FROM   DBO.PATIENT_LISTS
        WHERE  LIST_ID = @LIST_ID
      ) AS a
    JOIN  
      ( SELECT LIST_ID                    -- the filtered index
        FROM   DBO.PATIENT_LIST_BESPOKE   -- can be used
        WHERE  END_DTTM IS NULL           -- for the subquery
      ) AS b
    ON  a.LIST_ID = b.LIST_ID             -- and the join

UNION ALL

SELECT  
    a.LIST_ID
FROM    
      ( SELECT LIST_ID 
        FROM   DBO.PATIENT_LISTS
        WHERE  LIST_ID = @LIST_ID
      ) AS a
WHERE NOT EXISTS  
      ( SELECT *
        FROM   DBO.PATIENT_LIST_BESPOKE AS b
        WHERE  a.LIST_ID = b.LIST_ID         -- but not for this
      ) ;

2nqクエリの2番目の部分では、フィルター選択されたインデックスを使用できないことは今や明らかです。


詳細には、これらのクエリに関してLIST_ID、最初の表には4種類の値があります。

  • (a)2番目のテーブルに一致する行があり、すべての値END_DTTM IS NULL

  • (b)2番目のテーブルにwith END_DTTM IS NULLとwithの両方で一致する行がある値END_DTTM IS NOT NULL

  • (c)2番目のテーブルに一致する行があり、すべてがである値END_DTTM IS NOT NULL

  • (d)2番目のテーブルに一致する行がない値。

今、最初のクエリは、タイプ(a)と(b)のすべての値をおそらく何度も(2番目のテーブルにと一致する行がある限り)返しEND_DTTM IS NULL、タイプ(c)と(d)のすべての行を1回だけ返します(これは、外部結合の一致しない部分です)。

2番目のクエリは、タイプ(a)と(b)のすべての値を(場合によっては2番目のテーブルにと一致する行がある限り)何度も返しEND_DTTM IS NULL、タイプ(d)のすべての行を1回だけ返します。
それがあろうしない第二のテーブルに一致する行を見つけるために参加型(C)のいずれかの値を返す(これらはありますEND_DTTM IS NOT NULL)、それらは、後続によって除去されるWHERE句。

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