web-dev-qa-db-ja.com

インデックス付き/マテリアライズドビューで使用するために列を行に変換します

特定の_SQL Server_テーブルの列を行に変換したいのですが。テーブルは事実上監査テーブルです。特定のユーザー属性(たとえば、名、ミドルネーム、姓、自宅番号)の特定の古い値と新しい値を別々の列に格納します。

それぞれUNPIVOTと_CROSS APPLY_を使用して成功しました。ただし、大量のレコード(> 500k)とすべてを1つのクエリで実行する場合のパフォーマンスを向上させることを望んでいました。テーブルにはプライマリクラスタ化インデックスがあります。ただし、実行計画によれば、UNPIVOTまたは_CROSS APPLY_の場合、どちらもNested Loop (Left Outer Join)を31%のコストで実行しています。繰り返しになりますが、クエリが非常に遅くなるようなものではありませんが、達成できるすべてのパフォーマンスを精査したいと思います。

私の思考プロセスは、OK、おそらくインデックス付きビューを作成できれば、クエリは非常に速く実行されました。

問題はSQL Serverの_indexed views_(Oracle-speakのマテリアライズドビュー)には、UNPIVOTや_CROSS APPLY_などの多くの制限があります(完全なリストを参照 こちら ) 。 _CROSS APPLY_は実質的に_INNER JOIN_であり、結合はインデックス付きビューで許容されるため、少なくとも1つの優れた代替手段です。それでも、_CROSS APPLY_を_INNER JOIN_に書き換えるのは困難です。

これが表です(簡略化するために6つのレコードを表示していますが、500K以上のレコードがあると想像してください)。

UserAuditTbl

_RowID     UserID      UserAttribute     OldValue     NewValue
1         ID00184     First Name        John         Jon
2         ID00184     Last Name         NULL         Albert
3         ID00185     Home Phone        555-555-1122 555-555-1212
4         ID00188     Middle Name       Jesse        James      
5         ID00188     Cell Phone        555-555-1234 555-555-1555
6         ID19594     Zip Code          00000        90210
_

_CROSS APPLY_を使用すると、クエリは次のようになります。

_SELECT 
   RowID 
  ,UserID 
  ,Column 
  ,Value
FROM
  UserAuditTbl
CROSS APPLY (
  VALUES ('OldValue', OldValue), 'NewValue', NewValue)
) x (Column, Value)
WHERE
  Value IS NOT NULL
_

最初の質問は、基になるクエリをインデックス付きビューに組み込むことができるように列を行に変換できるかどうかです。そして2番目の質問は、インデックス付きビューを回避することが最善であるかどうかということですが、ここで私の選択肢は何ですか? SSISまたはトリガーを介してETLを新しいテーブルに?

お時間をいただきありがとうございます。

4
user3621633

あなたの説明に基づいて、インデックス付きビューがあなたの最良の解決策になる可能性はかなり低いと思います。インデックス付きビューは、UserAuditTblに書き込むたびにオーバーヘッドを追加します。最初のシークやフィルターのみに注意する場合、各行を2行にアンピボットするCROSS APPLYは非常に効率的です。必要な行(たとえば、関心のある(UserID, UserAttribute)タプルの行のみ)。

完全なテーブル(インデックスを含む)の完全な例、偽のデータを含む予想される行数をテーブルに入力するスクリプト、および最適化する必要がある正確なT-SQLを投稿すると、より具体的な情報を提供できる場合があります提案。

ただし、インデックス付きビューのアプローチを試すことにした場合、CROSS APPLYを結合として実装するかなり簡単な方法は、CROSS APPLYの値ごとに1行を含むテーブルを作成し、それに結合することです。テーブル。例えば:

必要なテーブルを作成します

/* Your sample data */
CREATE TABLE dbo.UserAuditTbl (
    RowID INT NOT NULL IDENTITY(1,1)
        CONSTRAINT PK_UserAuditTbl PRIMARY KEY,
    UserID VARCHAR(10) NOT NULL,
    UserAttribute VARCHAR(100) NOT NULL,
    OldValue VARCHAR(100) NOT NULL,
    NewValue VARCHAR(100) NOT NULL
)
INSERT INTO dbo.UserAuditTbl (UserID,UserAttribute,OldValue,NewValue)
VALUES  ('ID00184','FirstName','John','Jon'),
        ('ID00184','LastName','NULL','Albert'),
        ('ID00185','HomePhone','555-555-1122','555-555-1212'),
        ('ID00188','MiddleName','Jesse','James'),
        ('ID00188','CellPhone','555-555-1234','555-555-1555'),
        ('ID19594','ZipCode','00000','90210')
GO

/* Create a two-row table to implement the CROSS APPLY as a join */
CREATE TABLE dbo.OldAndNewValues (
    OldOrNewValue CHAR(8) NOT NULL
        CONSTRAINT PK_OldAndNewValues PRIMARY KEY
)
INSERT INTO dbo.OldAndNewValues (OldOrNewValue)
VALUES  ('OldValue'),
        ('NewValue')
GO

インデックス付きビューを作成

/* Create a view that implements the cross apply as a join */
CREATE VIEW dbo.UserAuditIndexedView WITH SCHEMABINDING AS
SELECT u.RowID,
    u.UserID,
    u.UserAttribute,
    v.OldOrNewValue AS Col,
    IIF(v.OldOrNewValue = 'OldValue', u.OldValue, u.NewValue) AS Val
FROM dbo.UserAuditTbl u
CROSS JOIN dbo.OldAndNewValues v
WHERE IIF(v.OldOrNewValue = 'OldValue', u.OldValue, u.NewValue) IS NOT NULL
GO

/* Create the view index.
    You can adjust the column order as necessary for your audit queries.
    Note that the first index on a view must be a unique index,
    so the RowID and Col columns must be used. */
CREATE UNIQUE CLUSTERED INDEX UQ_UserAuditIndexedView
ON dbo.UserAuditIndexedView (UserID, UserAttribute, RowID, Col)
GO

結果が一致することを確認

/* Use the view index */
SELECT *
FROM dbo.UserAuditIndexedView WITH (NOEXPAND)
ORDER BY RowID, Col

/* An adapted version of your original query (the original does not compile) */
SELECT RowID, UserID, UserAttribute, Col, Val
FROM dbo.UserAuditTbl
CROSS APPLY (
  VALUES ('OldValue', OldValue), ('NewValue', NewValue)
) x (Col, Val)
WHERE Val IS NOT NULL
ORDER BY RowID, Col
GO
1
Geoff Patterson