sql >> データベース >  >> RDS >> Sqlserver

SQL Server更新トリガー、変更されたフィールドのみを取得

    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','[email protected]',24);
    INSERT INTO Sample_Table VALUES (2,'Alice','Brown','2255','[email protected]',32);
    INSERT INTO Sample_Table VALUES (3,'Reg','Jones','2280','[email protected]',19);
    INSERT INTO Sample_Table VALUES (4,'Mary','Doe','2216','[email protected]',28);
    INSERT INTO Sample_Table VALUES (5,'Peter','Nash','2214','[email protected]',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','[email protected]',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ビットフィールドやarthオーバーフローの問題をいじることはありません。設計時に比較する列がわかっている場合は、動的SQLは必要ありません。

    欠点は、出力が異なる形式であり、すべてのフィールド値がsql_variantに変換されることです。最初の値は、出力を再度ピボットすることで修正でき、2番目の値は、知識に基づいて必要なタイプに再キャストすることで修正できます。テーブルの設計ですが、これらは両方とも複雑な動的SQLを必要とします。これらは両方とも、XML出力では問題にならない可能性があります。この質問は、出力を同じ形式に戻すのと似たようなことをします。

    編集:以下のコメントを確認してください。変更される可能性のある自然な主キーがある場合でも、この方法を使用できます。 NEWID()関数を使用して、デフォルトでGUIDが入力される列を追加する必要があります。次に、主キーの代わりにこの列を使用します。

    このフィールドにインデックスを追加することもできますが、トリガー内の削除および挿入されたテーブルはメモリ内にあるため、使用されず、パフォーマンスに悪影響を与える可能性があります。



    1. SQLで数値列の平均を見つける方法

    2. 日時値(SQL Server)の時間部分を削除するにはどうすればよいですか?

    3. PostgreSQL:大文字と小文字を区別しない文字列の比較

    4. サーバーを再起動せずにMySQLクエリキャッシュをクリアする