COLUMNS_UPDATED をまったく使用せず、実行時に動的 SQL を構築することに依存しない別の完全に異なるソリューションがあります。(設計時に動的 SQL を使用したい場合もありますが、それは別の話です。)
基本的に、挿入されたテーブルと削除されたテーブルから始めて、それぞれのテーブルをアンピボットして、それぞれに一意のキー、フィールド値、およびフィールド名の列が残るようにします。次に、2 つを結合して、変更されたものをフィルター処理します。
以下は、ログに記録される内容を表示するためのテスト呼び出しを含む、完全に機能する例です。
-- -------------------- Setup tables and some initial data --------------------
CREATE TABLE dbo.Sample_Table (ContactID int, Forename varchar(100), Surname varchar(100), Extn varchar(16), Email varchar(100), Age int );
INSERT INTO Sample_Table VALUES (1,'Bob','Smith','2295','bs@example.com',24);
INSERT INTO Sample_Table VALUES (2,'Alice','Brown','2255','ab@example.com',32);
INSERT INTO Sample_Table VALUES (3,'Reg','Jones','2280','rj@example.com',19);
INSERT INTO Sample_Table VALUES (4,'Mary','Doe','2216','md@example.com',28);
INSERT INTO Sample_Table VALUES (5,'Peter','Nash','2214','pn@example.com',25);
CREATE TABLE dbo.Sample_Table_Changes (ContactID int, FieldName sysname, FieldValueWas sql_variant, FieldValueIs sql_variant, modified datetime default (GETDATE()));
GO
-- -------------------- Create trigger --------------------
CREATE TRIGGER TriggerName ON dbo.Sample_Table FOR DELETE, INSERT, UPDATE AS
BEGIN
SET NOCOUNT ON;
--Unpivot deleted
WITH deleted_unpvt AS (
SELECT ContactID, FieldName, FieldValue
FROM
(SELECT ContactID
, cast(Forename as sql_variant) Forename
, cast(Surname as sql_variant) Surname
, cast(Extn as sql_variant) Extn
, cast(Email as sql_variant) Email
, cast(Age as sql_variant) Age
FROM deleted) p
UNPIVOT
(FieldValue FOR FieldName IN
(Forename, Surname, Extn, Email, Age)
) AS deleted_unpvt
),
--Unpivot inserted
inserted_unpvt AS (
SELECT ContactID, FieldName, FieldValue
FROM
(SELECT ContactID
, cast(Forename as sql_variant) Forename
, cast(Surname as sql_variant) Surname
, cast(Extn as sql_variant) Extn
, cast(Email as sql_variant) Email
, cast(Age as sql_variant) Age
FROM inserted) p
UNPIVOT
(FieldValue FOR FieldName IN
(Forename, Surname, Extn, Email, Age)
) AS inserted_unpvt
)
--Join them together and show what's changed
INSERT INTO Sample_Table_Changes (ContactID, FieldName, FieldValueWas, FieldValueIs)
SELECT Coalesce (D.ContactID, I.ContactID) ContactID
, Coalesce (D.FieldName, I.FieldName) FieldName
, D.FieldValue as FieldValueWas
, I.FieldValue AS FieldValueIs
FROM
deleted_unpvt d
FULL OUTER JOIN
inserted_unpvt i
on D.ContactID = I.ContactID
AND D.FieldName = I.FieldName
WHERE
D.FieldValue <> I.FieldValue --Changes
OR (D.FieldValue IS NOT NULL AND I.FieldValue IS NULL) -- Deletions
OR (D.FieldValue IS NULL AND I.FieldValue IS NOT NULL) -- Insertions
END
GO
-- -------------------- Try some changes --------------------
UPDATE Sample_Table SET age = age+1;
UPDATE Sample_Table SET Extn = '5'+Extn where Extn Like '221_';
DELETE FROM Sample_Table WHERE ContactID = 3;
INSERT INTO Sample_Table VALUES (6,'Stephen','Turner','2299','st@example.com',25);
UPDATE Sample_Table SET ContactID = 7 where ContactID = 4; --this will be shown as a delete and an insert
-- -------------------- See the results --------------------
SELECT *, SQL_VARIANT_PROPERTY(FieldValueWas, 'BaseType') FieldBaseType, SQL_VARIANT_PROPERTY(FieldValueWas, 'MaxLength') FieldMaxLength from Sample_Table_Changes;
-- -------------------- Cleanup --------------------
DROP TABLE dbo.Sample_Table; DROP TABLE dbo.Sample_Table_Changes;
したがって、bigint ビットフィールドやアート オーバーフローの問題をいじる必要はありません。設計時に比較する列がわかっている場合は、動的 SQL は必要ありません。
欠点は、出力が異なる形式であり、すべてのフィールド値が sql_variant に変換されることです。1 つ目は出力を再度ピボットすることで修正でき、2 つ目はデータの知識に基づいて必要な型に再キャストすることで修正できます。テーブルの設計が必要ですが、どちらも複雑な動的 SQL を必要とします。これらは両方とも、XML 出力では問題にならない場合があります。この質問は、出力を同じ形式に戻すことに似ています。
編集:以下のコメントを確認すると、変更される可能性のある自然な主キーがある場合でも、このメソッドを使用できます。NEWID() 関数を使用して、デフォルトで GUID が設定されている列を追加するだけです。その後、主キーの代わりにこの列を使用します。
このフィールドにインデックスを追加することもできますが、トリガーで削除および挿入されたテーブルはメモリ内にあるため、使用されず、パフォーマンスに悪影響を及ぼす可能性があります。