web-dev-qa-db-ja.com

永続的な計算列のインデックスはシークできません

Addressという永続的な計算列があるHashkeyというテーブルがあります。列は確定的ですが正確ではありません。シークできない一意のインデックスがあります。このクエリを実行すると、主キーが返されます。

SELECT @ADDRESSID= ISNULL(AddressId,0)
FROM dbo.[Address]
WHERE HashKey = @HashKey

私はこの計画を手に入れました:

BasicPlan

インデックスを強制すると、さらに悪い計画になります:

ForceIndex

インデックスとシークの両方を強制しようとすると、エラーが発生します。

このクエリでヒントが定義されているため、クエリプロセッサはクエリプランを作成できませんでした。ヒントを指定せず、SET FORCEPLANを使用せずにクエリを再送信します

これは正確ではないからですか?それが持続するならそれは問題ではないと思いましたか?

これを非計算列にせずに、このインデックスをシーク可能にする方法はありますか?

これに関する情報へのリンクはありますか?

実際のテーブル作成を投稿することはできませんが、同じ問題があるテストテーブルは次のとおりです。

drop TABLE [dbo].[Test]

CREATE TABLE [dbo].[Test]
  (
     [test]        [VARCHAR](100) NULL,
     [TestGeocode] [geography] NULL,
     [Hashkey] AS CAST(
                        ( hashbytes
                            ('SHA', 
                                ( RIGHT(REPLICATE(' ', (100)) + isnull([test], ''), ( 100 )) ) 
                                + RIGHT(REPLICATE(' ', (100)) + isnull([TestGeocode].[ToString](), ''), ( 100 ))
                            ) 
                        ) AS BINARY(20)                                                                                                        
                      ) PERSISTED
    CONSTRAINT [UK_Test_HashKey] UNIQUE NONCLUSTERED([Hashkey])
  )    
GO    
DECLARE @Hashkey BINARY(20)

SELECT [Hashkey]
FROM   [dbo].[Test] WITH (FORCESEEK) /*Query processor could not produce a query plan*/
WHERE  [Hashkey] = @Hashkey 
15
user3593990

この問題は、[TestGeocode].[ToString]()maxデータ型(nvarchar(max))を返すという事実に関連しているようです。

この単純なバージョンでも問題が発生します(_c1_の定義をvarchar(8000)に変更するか、COALESCEの代わりにISNULLを使用すると解決します)

_DROP TABLE dbo.Test

CREATE TABLE dbo.Test
  (
     c1        VARCHAR(
                          MAX    --Fails
                        --  8000 --Works fine
                          ) NULL,
     comp1 AS CAST(ISNULL(c1, 'ABC') AS VARCHAR(100))
    CONSTRAINT UK_Test_comp1 UNIQUE NONCLUSTERED(comp1)
  )

GO

DECLARE @comp1 VARCHAR(100)

SELECT comp1
FROM   dbo.Test WITH (FORCESEEK)
WHERE  comp1 = @comp1 
OPTION (QUERYTRACEON 3604, QUERYTRACEON 8606); 
_

計算された列の参照は、基になる定義に展開され、後で列に再度照合されます。これにより、計算された列を名前でまったく参照せずに一致させることができます。また、基礎となる定義を単純化して操作できます。

ISNULLは、最初のパラメーター(この例ではVARCHAR(MAX))のデータ型を返します。ここでもCOALESCEの戻り値の型はVARCHAR(MAX)になりますが、問題を回避する方法で異なる方法で評価されているようです。

クエリが成功した場合、トレースフラグの出力には以下が含まれます。

_ScaOp_Convert varchar(max) collate 49160,Null,Var,Trim,ML=65535

    ScaOp_Const TI(varchar collate 49160,Var,Trim,ML=3) 
                      XVAR(varchar,Owned,Value=Len,Data = (3,ABC))
_

失敗した場合、これは

_ScaOp_Identifier COL: ConstExpr1003 
_

Ispeculate失敗した場合、(暗黙の)CAST('ABC' AS VARCHAR(MAX))は一度だけ実行され、これはランタイム定数として評価されます( 詳細 )。ただし、実際の文字列リテラル値自体ではなく、このランタイム定数ラベルへの参照により、計算された列の定義と一致しなくなります。

この書き換えにより、クエリの問題が回避されます

_CREATE TABLE [dbo].[Test]
  (
     [test]        [VARCHAR](100) NULL,
     [TestGeocode] [geography] NULL,
     [Hashkey] AS CAST(
                        ( hashbytes
                            ('SHA', 
                                ( RIGHT(SPACE(100) + isnull([test], ''), 100) ) 
                                + RIGHT(SPACE(100) + isnull(CAST(RIGHT([TestGeocode].[ToString](),100) AS VARCHAR(100)), ''),100)
                            ) 
                        ) AS BINARY(20)                                                                                                        
                      ) PERSISTED
    CONSTRAINT [UK_Test_HashKey] UNIQUE NONCLUSTERED([Hashkey])
  )
_
12
Martin Smith

@HashKeyのデータ型がインデックス付きの列のデータ型と一致しない場合、引数を使用できない式が原因でこれらの症状が発生します。必要なデータ型を強制するために、計算列式で明示的なCASTが必要になる場合があります。

あなたの再現に基づいて、これはバグだと思います。 Martinの回避策のバージョンも含めて、Connectのバグ Computed Column Index Not Used を提出しました。自由に投票してください。

0
Dan Guzman