特定の_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を新しいテーブルに?
お時間をいただきありがとうございます。
あなたの説明に基づいて、インデックス付きビューがあなたの最良の解決策になる可能性はかなり低いと思います。インデックス付きビューは、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