sql >> Base de Datos >  >> RDS >> Sqlserver

Desencadenador de actualización de SQL Server, Obtener solo campos modificados

Tengo otra solución completamente diferente que no usa COLUMNS_UPDATED en absoluto, ni se basa en la creación de SQL dinámico en tiempo de ejecución. (Es posible que desee utilizar SQL dinámico en el momento del diseño, pero esa es otra historia).

Básicamente, comienza con las tablas insertadas y eliminadas, quita el pivote de cada una de ellas para que solo le queden las columnas únicas de clave, valor de campo y nombre de campo para cada una. Luego, une los dos y filtra cualquier cosa que haya cambiado.

Aquí hay un ejemplo de trabajo completo, que incluye algunas llamadas de prueba para mostrar lo que se registra.

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

Así que no se meta con los problemas de desbordamiento de arth y campos de bits bigint. Si conoce las columnas que desea comparar en el momento del diseño, no necesita ningún SQL dinámico.

En el lado negativo, la salida tiene un formato diferente y todos los valores de los campos se convierten a sql_variant, el primero se puede arreglar girando la salida nuevamente, y el segundo se puede arreglar volviendo a los tipos requeridos según su conocimiento del diseño de la tabla, pero ambos requerirían un sql dinámico complejo. Ambos pueden no ser un problema en su salida XML. Esta pregunta hace algo similar a recuperar la salida en el mismo formato.

Editar:revisando los comentarios a continuación, si tiene una clave principal natural que podría cambiar, aún puede usar este método. Solo necesita agregar una columna que se complete de forma predeterminada con un GUID mediante la función NEWID(). A continuación, utilice esta columna en lugar de la clave principal.

Es posible que desee agregar un índice a este campo, pero como las tablas eliminadas e insertadas en un disparador están en la memoria, es posible que no se usen y que tengan un efecto negativo en el rendimiento.