web-dev-qa-db-ja.com

計算列にフィルターインデックスを作成できません

私の以前の質問 新しい計算された列をテーブルに追加する間、ロックのエスカレーションを無効にすることは良い考えですか? 、計算された列を作成しています:

ALTER TABLE dbo.tblBGiftVoucherItem
ADD isUsGift AS CAST
(
    ISNULL(
        CASE WHEN sintMarketID = 2 
            AND strType = 'CARD'
            AND strTier1 LIKE 'GG%' 
        THEN 1 
        ELSE 0 
        END
    , 0) 
    AS BIT
) PERSISTED;

計算された列はPERSISTEDであり、- computed_column_definition(Transact-SQL) に従います:

持続した

データベースエンジンが計算値をテーブルに物理的に格納し、計算列が依存する他の列が更新されたときに値を更新することを指定します。計算列にPERSISTEDのマークを付けると、確定的ではあるが正確ではない計算列にインデックスを作成できます。詳細については、計算列のインデックスを参照してください。パーティションテーブルのパーティション列として使用される計算列は、明示的にPERSISTEDとマークする必要があります。 PERSISTEDが指定されている場合、computed_column_expressionは確定的でなければなりません。

しかし、列にインデックスを作成しようとすると、次のエラーが発生します。

CREATE INDEX FIX_tblBGiftVoucherItem_incl
ON dbo.tblBGiftVoucherItem (strItemNo) 
INCLUDE (strTier3)
WHERE isUsGift = 1;

フィルターされたインデックス 'FIX_tblBGiftVoucherItem_incl'は、テーブル 'dbo.tblBGiftVoucherItem'に作成できません。フィルター式の列 'isUsGift'が計算された列だからです。この列が含まれないようにフィルター式を書き直してください。

計算列にフィルター処理されたインデックスを作成するにはどうすればよいですか?

または

代替ソリューションはありますか?

18

残念ながら、SQL Server 2014の時点では、フィルターが計算列に存在するFiltered Indexを作成する機能はありません(永続化されているかどうかに関係なく)。

Connect Item は2009年からオープンしているので、是非投票してください。たぶんマイクロソフトはこれをいつか修正するでしょう。

Aaron Bertrandが Filtered Indexes に関する他の多くの問題をカバーする記事を公開しています。

21
Mark Sinkinson

永続化された列にフィルター選択されたインデックスを作成することはできませんが、使用できるかなり単純な回避策があります。

テストとして、IDENTITY列と、identity列に基づく永続的な計算列を含む単純なテーブルを作成しました。

_USE tempdb;

CREATE TABLE dbo.PersistedViewTest
(
    PersistedViewTest_ID INT NOT NULL
        CONSTRAINT PK_PersistedViewTest
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , SomeData VARCHAR(2000) NOT NULL
    , TestComputedColumn AS (PersistedViewTest_ID - 1) PERSISTED
);
GO
_

次に、計算列にフィルターを設定したテーブルに基づいてスキーマバインドビューを作成しました。

_CREATE VIEW dbo.PersistedViewTest_View
WITH SCHEMABINDING
AS
SELECT PersistedViewTest_ID
    , SomeData 
    , TestComputedColumn
FROM dbo.PersistedViewTest
WHERE TestComputedColumn < CONVERT(INT, 27);
_

次に、スキーマバインドビューにクラスター化インデックスを作成しました。これには、計算列の値を含め、ビューに格納されている値を永続化する効果があります。

_CREATE UNIQUE CLUSTERED INDEX IX_PersistedViewTest
ON dbo.PersistedViewTest_View(PersistedViewTest_ID);
GO
_

テーブルにテストデータを挿入します。

_INSERT INTO dbo.PersistedViewTest (SomeData)
SELECT o.name + o1.name + o2.name
FROM sys.objects o
    CROSS JOIN sys.objects o1
    CROSS JOIN sys.objects o2;
_

ビューに統計項目とインデックスを作成します。

_CREATE STATISTICS ST_PersistedViewTest_View
ON dbo.PersistedViewTest_View(TestComputedColumn)
WITH FULLSCAN;

CREATE INDEX IX_PersistedViewTest_View_TestComputedColumn
ON dbo.PersistedViewTest_View(TestComputedColumn);
_

永続化列mayを持つテーブルに対してSELECTステートメントを実行すると、クエリオプティマイザが意味をなすと判断した場合、自動的に永続化ビューを使用するようになりました。そうする:

_SELECT pv.PersistedViewTest_ID
    , pv.TestComputedColumn
FROM dbo.PersistedViewTest pv
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
_

上記のクエリの実際の実行プランは、クエリオプティマイザーが永続化ビューを使用して結果を返すことを選択したことを示しています。

enter image description here

上記のWHERE句の明示的な変換に気づいたかもしれません。この明示的なCONVERT(INT, 26)により、クエリオプティマイザーは統計オブジェクトを適切に使用して、クエリによって返される行数を推定できます。 _WHERE pv.TestComputedColumn = 26_を使用してクエリを記述する場合、26は実際には_TINY INT_と見なされるため、クエリオプティマイザーは行数を適切に推定できない場合があります。これにより、SQL Serverが永続化ビューを使用しない可能性があります。暗黙的な変換は非常に骨が折れる可能性があり、比較と結合に正しいデータ型を一貫して使用することはお金のかかることです。

もちろん、スキーマバインディングを使用した結果として生じるすべての標準的な「問題」は、上記のシナリオに当てはまります。これにより、すべてのシナリオでこの回避策を使用できなくなる可能性があります。たとえば、最初にビューからスキーマバインディングを削除しないと、ベーステーブルを変更できなくなります。これを行うには、クラスター化インデックスをビューから削除する必要があります。

SQL Server Enterprise Editionがない場合、クエリオプティマイザーは、WITH (NOEXPAND)ヒントを使用してビューを直接参照しないクエリに対して、永続ビューを自動的に使用しません。 Enterprise Edition以外のバージョンで永続化ビューを使用する利点を理解するには、上記のクエリを次のように書き直す必要があります。

_SELECT pv.PersistedViewTest_ID
    , pv.TestComputedColumn
FROM dbo.PersistedViewTest_View pv WITH (NOEXPAND)
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
_

上記のEnterprise Editionの制限を指摘してくれた Ian Ringrose に、_(NOEXPAND)_ヒ​​ントに Paul White をありがとう。

Paulによるこの回答 には、永続化されたビューに関連するクエリオプティマイザーに関する興味深い詳細があります。

21
Max Vernon

Create Indexおよびそのwhereclauseから、これは不可能です。

どこ

インデックスに含める行を指定して、フィルター選択されたインデックスを作成します。フィルター選択されたインデックスは、テーブルの非クラスター化インデックスである必要があります。フィルターされたインデックスのデータ行のフィルターされた統計を作成します。

フィルター述部は単純な比較ロジックを使用し、計算列、UDT列、空間データタイプの列、またはhierarchyIDデータタイプの列を参照できません。 NULLリテラルを使用した比較は、比較演算子では許可されていません。代わりにIS NULLおよびIS NOT NULL演算子を使用してください。

ソース: [〜#〜] msdn [〜#〜]

5

これは Max Vernonの回避策 を改善する試みです。彼のソリューションでは、ビューの2つのインデックスと統計オブジェクトの使用を提案しています。

最初のインデックスはクラスター化されています。これは、テーブルの非クラスター化インデックスとは異なり、最初にクラスター化インデックスがない状態でビューに非クラスター化インデックスを作成しようとするとエラーが発生するため、実際に必要です。

2番目のインデックスは非クラスター化インデックスで、クエリの背後のインデックスとして使用されます。彼の回答のコメントセクションで、非クラスター化インデックスの代わりにクラスター化インデックスを使用するとどうなるかを尋ねました。

次の分析は、この質問に答えようとしています。

ビューに非クラスター化インデックスを作成しないことを除いて、彼のまったく同じコードを使用しています。

また、統計オブジェクトを作成していません。従い、SQL Server Management Studio(SSMS)を使用して以下のコードを入力する場合、エラーのように見える赤い波線が表示される場合があることに注意してください。これらは(おそらく)エラーではありませんが、インテリセンスの問題が関係しています。

インテリセンスを無効にするか、エラーを無視してコマンドを実行することができます。エラーなしで完了するはずです。

-- Create the test table that uses a computed column.
USE tempdb;
CREATE TABLE dbo.PersistedViewTest
(
    PersistedViewTest_ID INT NOT NULL
    CONSTRAINT PK_PersistedViewTest
    PRIMARY KEY CLUSTERED
    IDENTITY(1,1)
    , SomeData VARCHAR(2000) NOT NULL
    , TestComputedColumn AS (PersistedViewTest_ID - 1) PERSISTED
);
GO

-- Insert some test data into the table.
INSERT INTO dbo.PersistedViewTest (SomeData)
SELECT o.name + o1.name + o2.name
FROM sys.objects o
    CROSS JOIN sys.objects o1
    CROSS JOIN sys.objects o2;
GO

次の実行プラン(ビュー/インデックスビューなし)は、テーブルに対して次のクエリが実行された後に作成されます。

SELECT pv.PersistedViewTest_ID, pv.TestComputedColumn
FROM dbo.PersistedViewTest pv
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
GO

enter image description here

これは、比較対象となるベースラインを提供します。クエリの完了後、統計オブジェクトが作成されたことに注意してください(_WA_Sys_00000003_1FCDBCEB)。 PK_PersistedViewTest統計オブジェクトは、クラスター化テーブルインデックスの作成時に作成されました。

次に、フィルターされたビューとそのビューのクラスター化インデックスが作成されます。

-- Create filtered view on the computed column.
CREATE VIEW dbo.PersistedViewTest_View
WITH SCHEMABINDING
AS
SELECT PersistedViewTest_ID, SomeData, TestComputedColumn
FROM dbo.PersistedViewTest
WHERE TestComputedColumn < CONVERT(INT, 27);
GO

-- Create unique clustered index to persist the values, including the computed column.
CREATE UNIQUE CLUSTERED INDEX IX_PersistedViewTest
ON dbo.PersistedViewTest_View(PersistedViewTest_ID);
GO

さて、もう一度クエリを実行してみましょうが、今回はビューに対して:

SELECT pv.PersistedViewTest_ID, pv.TestComputedColumn
FROM dbo.PersistedViewTest_View pv
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
GO

新しい実行計画は次のとおりです。

enter image description here

新しい計画が信頼できる場合は、ビューとそのビューにクラスター化インデックスを追加した後、統計に、クエリの実行に必要な時間が2倍になったことが示されます。また、クエリの実行後、新しいインデックスをサポートするための新しい統計オブジェクトが作成されなかったことにも注意してください。これは、テーブルに対するクエリとは異なります。

クエリプランは、非クラスター化インデックスの作成がクエリのパフォーマンスの向上に非常に役立つことを依然として示唆しています。それでは、目的のパフォーマンスを向上させるには、非クラスター化インデックスをビューに追加する必要があるということですか。最後に試すべきことが1つあります。 「WITH NOEXPAND」オプションを使用するようにクエリを変更します。

SELECT pv.PersistedViewTest_ID, pv.TestComputedColumn
FROM dbo.PersistedViewTest_View pv WITH (NOEXPAND)
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
GO

これにより、次のクエリプランが作成されます。

enter image description here

この実行プランは、Max Vernonの回答で与えられた非クラスター化インデックスを使用して作成されたものと非常に似ています。しかし、これは1つ少ない(非クラスター化)インデックスと1つ少ない統計オブジェクトで行われます。

インデックス付きビューを適切に使用するには、SQL Serverの高速バージョンと標準バージョンでNOEXPANDオプションを使用する必要があることがわかります。 Paul Whiteには 優れた記事 があり、NOEXPANDオプションを使用する利点について説明しています。彼はまた 推奨 このオプションをエンタープライズエディションで使用して、ビューインデックスによって提供される一意性の保証がオプティマイザによって使用されるようにします。

上記の分析はSQL Sever 2014のExpressエディションで行われました。SQLServer 2016のDeveloperエディションでも試してみました。NOEXPANDオプションは、パフォーマンスを向上させるために開発版では必要ないようですが、それでも推奨されます。

5か月も経たないうちに、マイクロソフトは 開発者向けエディションを無料 にしました。ライセンスにより、使用は開発のみに制限されます。つまり、データベースは実稼働環境では使用できません。したがって、メモリ最適化テーブル、暗号化、Rなどをテストすることを検討している場合は、ライセンスなしの言い訳はもうありません。数日前にSQL Server 2014 Expressと一緒に問題なくコンピューターにインストールしました。

3
Bob Bryan
  • フィルター処理されたインデックスを配置するために計算されていない列が必要です。
  • その列に入る値を計算する必要があります。

列を計算する前に、行が変更または挿入されるたびにtriggersを使用して列の値を計算しました。

(トリガーを使用して、2番目のテーブルからアイテムのPKを挿入/削除し、クエリで使用することもできます。

3
Ian Ringrose