Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Déclencheur de mise à jour SQL Server, obtenir uniquement les champs modifiés

J'ai une autre solution complètement différente qui n'utilise pas du tout COLUMNS_UPDATED, ni ne repose sur la construction de SQL dynamique au moment de l'exécution. (Vous voudrez peut-être utiliser SQL dynamique au moment de la conception, mais c'est une autre histoire.)

Fondamentalement, vous commencez par les tables insérées et supprimées, dé-pivotez chacune d'entre elles afin qu'il ne vous reste plus que la clé unique, la valeur du champ et les colonnes de nom de champ pour chacune. Ensuite, vous joignez les deux et filtrez tout ce qui a changé.

Voici un exemple de travail complet, y compris quelques appels de test pour montrer ce qui est enregistré.

-- -------------------- 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;

Donc, pas de problème avec les champs de bits bigint et les problèmes de débordement d'arth. Si vous connaissez les colonnes que vous souhaitez comparer au moment de la conception, vous n'avez pas besoin de SQL dynamique.

En revanche, la sortie est dans un format différent et toutes les valeurs de champ sont converties en sql_variant, la première pourrait être corrigée en faisant pivoter à nouveau la sortie, et la seconde pourrait être corrigée en refondant les types requis en fonction de votre connaissance du conception de la table, mais ces deux éléments nécessiteraient un SQL dynamique complexe. Ces deux éléments peuvent ne pas être un problème dans votre sortie XML. Cette question fait quelque chose de similaire à la récupération de la sortie dans le même format.

Modifier :en examinant les commentaires ci-dessous, si vous avez une clé primaire naturelle qui pourrait changer, vous pouvez toujours utiliser cette méthode. Il vous suffit d'ajouter une colonne qui est remplie par défaut avec un GUID à l'aide de la fonction NEWID(). Vous utilisez ensuite cette colonne à la place de la clé primaire.

Vous voudrez peut-être ajouter un index à ce champ, mais comme les tables supprimées et insérées dans un déclencheur sont en mémoire, il se peut qu'il ne soit pas utilisé et qu'il ait un effet négatif sur les performances.