sql >> Databasteknik >  >> RDS >> Sqlserver

SQL Server Update Trigger, få endast modifierade fält

Jag har en annan helt annan lösning som inte använder COLUMNS_UPDATED alls, och inte heller förlitar sig på att bygga dynamisk SQL vid körning. (Du kanske vill använda dynamisk SQL vid designtillfället, men det är en annan historia.)

I princip börjar du med de infogade och borttagna tabellerna, avpivoterar var och en av dem så att du bara har den unika nyckeln, fältvärdet och fältnamnskolumnerna för var och en. Sedan slår du ihop de två och filtrerar efter allt som har ändrats.

Här är ett fullständigt fungerande exempel, inklusive några testanrop för att visa vad som loggas.

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

Så inget krångel med bigint bitfält och arth overflow problem. Om du vet vilka kolumner du vill jämföra vid designtillfället behöver du ingen dynamisk SQL.

På nackdelen är utdata i ett annat format och alla fältvärden konverteras till sql_variant, det första kan fixas genom att svänga utdatat igen och det andra kan fixas genom att omarbeta tillbaka till de nödvändiga typerna baserat på din kunskap om designen av tabellen, men båda dessa skulle kräva lite komplex dynamisk sql. Båda dessa kanske inte är ett problem i din XML-utdata. Den här frågan gör något som liknar att få tillbaka utdata i samma format.

Redigera:Granska kommentarerna nedan, om du har en naturlig primärnyckel som kan ändras kan du fortfarande använda den här metoden. Du behöver bara lägga till en kolumn som är fylld som standard med en GUID med hjälp av NEWID()-funktionen. Du använder sedan denna kolumn i stället för primärnyckeln.

Du kanske vill lägga till ett index i det här fältet, men eftersom de raderade och infogade tabellerna i en trigger finns i minnet kanske det inte används och kan ha en negativ effekt på prestandan.



  1. Få extra rader - Efter att ha sammanfogat de 3 borden med Left Join

  2. Hur justify_days() fungerar i PostgreSQL

  3. Oracle (+) Operatör

  4. Hur man krypterar hybrid molndatabastrafik