Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Trigger di aggiornamento di SQL Server, Ottieni solo campi modificati

Ho un'altra soluzione completamente diversa che non utilizza affatto COLUMNS_UPDATED, né si basa sulla creazione di SQL dinamico in fase di esecuzione. (Potresti voler utilizzare SQL dinamico in fase di progettazione, ma questa è un'altra storia.)

Fondamentalmente inizi con le tabelle inserite ed eliminate, annulla il pivot in ciascuna di esse in modo da rimanere con la chiave univoca, il valore del campo e le colonne del nome del campo per ciascuna. Quindi unisci i due e filtra tutto ciò che è cambiato.

Ecco un esempio funzionante completo, incluse alcune chiamate di prova per mostrare cosa viene registrato.

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

Quindi non scherzare con i bitfield bigint e i problemi di overflow dell'arth. Se si conoscono le colonne che si desidera confrontare in fase di progettazione, non è necessario alcun SQL dinamico.

Sul lato negativo, l'output è in un formato diverso e tutti i valori dei campi vengono convertiti in sql_variant, il primo potrebbe essere corretto ruotando nuovamente l'output e il secondo potrebbe essere corretto riformulando i tipi richiesti in base alla tua conoscenza del design del tavolo, ma entrambi richiederebbero un complesso sql dinamico. Entrambi potrebbero non essere un problema nell'output XML. Questa domanda fa qualcosa di simile a riportare l'output nello stesso formato.

Modifica:esaminando i commenti di seguito, se hai una chiave primaria naturale che potrebbe cambiare, puoi comunque utilizzare questo metodo. Devi solo aggiungere una colonna che viene popolata per impostazione predefinita con un GUID utilizzando la funzione NEWID(). Quindi utilizzare questa colonna al posto della chiave primaria.

Potresti voler aggiungere un indice a questo campo, ma poiché le tabelle eliminate e inserite in un trigger sono in memoria, potrebbero non essere utilizzate e potrebbero avere un effetto negativo sulle prestazioni.