計算列インデックスは使用されません


14

2つの列が等しいかどうかに基づいて高速ルックアップが必要です。インデックス付きの計算列を使用しようとしましたが、SQL Serverはそれを使用していないようです。静的に設定されたインデックス付きのビット列を使用するだけで、予想されるインデックスシークが得られます。

このような質問は他にもありますが、インデックスが使用されない理由に焦点を当てたものはありません。

テスト表:

CREATE TABLE dbo.Diffs
    (
    Id int NOT NULL IDENTITY (1, 1),
    DataA int NULL,
    DataB int NULL,
    DiffPersisted  AS isnull(convert(bit, case when [DataA] is null and [DataB] is not null then 1 when [DataA] <> [DataB] then 1 else 0 end), 0) PERSISTED ,
    DiffComp  AS isnull(convert(bit, case when [DataA] is null and [DataB] is not null then 1 when [DataA] <> [DataB] then 1 else 0 end), 0),
    DiffStatic bit not null,
    Primary Key (Id)
    )

create index ix_DiffPersisted on Diffs (DiffPersisted)
create index ix_DiffComp on Diffs (DiffComp)
create index ix_DiffStatic on Diffs (DiffStatic)

そしてクエリ:

select Id from Diffs where DiffPersisted = 1
select Id from Diffs where DiffComp = 1
select Id from Diffs where DiffStatic = 1

そして、結果の実行計画: 実行計画

回答:


10

COALESCE代わりに試してくださいISNULL。を使用するとISNULL、SQL Serverはより狭いインデックスに対して述語をプッシュできないようであるため、クラスター化されたものをスキャンして情報を見つける必要があります。

CREATE TABLE dbo.Diffs
    (
    Id int NOT NULL IDENTITY (1, 1),
    DataA int NULL,
    DataB int NULL,
    DiffPersisted  AS COALESCE(convert(bit, case when [DataA] is null 
      and [DataB] is not null then 1 when [DataA] <> [DataB] 
      then 1 else 0 end), 0) PERSISTED ,
    DiffComp  AS COALESCE(convert(bit, case when [DataA] is null 
      and [DataB] is not null then 1 when [DataA] <> [DataB] 
      then 1 else 0 end), 0),
    DiffStatic bit not null,
    Primary Key (Id)
    );

ただし、静的列を使用する場合、フィルター選択されたインデックスの方が意味があり、I / Oコストが低くなります(すべてがフィルター述語に一致する行の数によって異なります)。

CREATE INDEX ix_DiffStaticFiltered 
  ON dbo.Diffs(DiffStatic)
  WHERE DiffStatic = 1;

非常に興味深い、これを考えていないだろう。COALESCEただし、この時点で単に削除できるようです。CASEステートメントは、0またはを返すことがすでに保証されていると思いますが、SQL Serverが計算列に対してnull不可を生成するためにのみ存在していると思います。ただし、null許容列は引き続き生成されます。そのため、の有無にかかわらず、この変更の1つの影響は、計算列がNULL可能になりましたが、インデックスシークを使用できることです。1ISNULLBITCOALESCECOALESCE
ジェフパターソン

@Geoffはい、それは本当です。しかし、我々は計算列定義NULLで知っているので、この場合には、本当に可能な出力はありませんが、これだけは本当に私たちは、SELECT INTOのソースとしてこの表を使用している場合は問題になります。
アーロンバートランド

これはいくつかの素晴らしい情報です-ありがとう!私の最終目標は、DataA列とDataB列を「ダーティ」なuuidとして使用して、レコードの非正規化列の非同期更新を可能にすることです。したがって、Diffフラグが1の場合は多すぎないはずです。フィールド、その後、2つのuuidを監視してフィールドを更新するトリガーを追加することを考えていました。
デビッドファイブル

また、@ GeoffPattersonが指摘したように、私は使用できませんCOALESCEか?なぜそれを保持するのですか?
デビッドファイブル

@DavidおそらくドロップできCOALESCEます。元のコードの見た目と意図を維持しようとしましたが、それなしではテストしませんでした。(ISNULL最初にそこにいた理由も説明できません。)
アーロンバートランド

5

これは、最も外側ISNULLが使用され、列のデータ型がである場合のSQL Server計算列一致ロジックの特定の制限ですbit

バグレポート

この問題を回避するには、次の回避策のいずれかを使用できます。

  1. 最外部を使用しないでください ISNULL(計算列を作成する唯一の方法)NOT NULL
  2. bit計算列の最終型としてデータ型を使用しないでください。
  3. 計算列PERSISTEDを作成し、トレースフラグ174有効にします

詳細

問題の核心は、トレースフラグ174がなければ、クエリ内のすべての計算列参照(永続的であっても)が常にクエリのコンパイルの非常に早い段階で基礎となる定義に展開されることです。

拡張の考え方は、列名だけではなく、定義に対してのみ機能する単純化と書き換えを可能にするということです。たとえば、計算の一部を冗長にしたり、その他の制約を課したりする可能性のある計算列を参照する述語がクエリに存在する場合があります。

早期の簡略化と書き換えが考慮されると、クエリコンパイルはクエリの式を計算列(クエリテキストで最初に見つかったものだけでなく、すべての計算列)に一致させようとします。

変更されていない計算列式は、ほとんどの場合、問題なく元の計算列と一致します。bit型の式と最も外側の式の一致に固有の場合、バグがあるようISNULLです。この特定のケースでは、内部の詳細な調査で成功する必要があることが示されている場合でも、マッチングは失敗します。

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