web-dev-qa-db-ja.com

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

以下のフィルターされたインデックスを作成しましたが、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   
10
chris

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

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

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

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

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

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

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

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

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

背景と詳細情報:

12
Paul White 9

これらはではないクエリが意味的に同じである。なぜなら、一方は結合の前にフィルタリングでき、もう一方は後にフィルタリングできるからである。 。簡単な例で説明します。

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の場合は大きな違いがあります。また、フィルター処理されたインデックスを使用できるかどうかよりも、結果が正しいことに注意する必要があります。

9
Aaron Bertrand

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番目の部分では、フィルター選択されたインデックスを使用できないことは今や明らかです。


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

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

  • (b)2番目のテーブルにEND_DTTM IS NULLEND_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回だけ返します。
notはタイプ(c)の任意の値を返します。これは、結合が2番目のテーブルで一致する行を検出し(ただし、これらにはEND_DTTM IS NOT NULLが含まれるため)、次のように削除されます。後続のWHERE句。

3
ypercubeᵀᴹ