Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Acionador de atualização do SQL Server, obter apenas campos modificados


Eu tenho outra solução completamente diferente que não usa COLUMNS_UPDATED, nem depende da construção de SQL dinâmico em tempo de execução. (Você pode querer usar SQL dinâmico em tempo de design, mas isso é outra história.)

Basicamente, você começa com as tabelas inseridas e excluídas, desarticula cada uma delas para que você fique apenas com a chave exclusiva, o valor do campo e as colunas de nome do campo para cada uma. Então você junta os dois e filtra por qualquer coisa que tenha mudado.

Aqui está um exemplo completo de trabalho, incluindo algumas chamadas de teste para mostrar o que está registrado.
-- -------------------- 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;

Portanto, não brinque com campos de bits bigint e problemas de estouro de arte. Se você conhece as colunas que deseja comparar em tempo de design, não precisa de nenhum SQL dinâmico.

No lado negativo, a saída está em um formato diferente e todos os valores de campo são convertidos em sql_variant, o primeiro pode ser corrigido girando a saída novamente e o segundo pode ser corrigido reformulando de volta para os tipos necessários com base em seu conhecimento do design da tabela, mas ambos exigiriam algum sql dinâmico complexo. Ambos podem não ser um problema em sua saída XML. Esta questão faz algo semelhante a obter a saída de volta no mesmo formato.

Editar:Revendo os comentários abaixo, se você tiver uma chave primária natural que possa mudar, ainda poderá usar esse método. Você só precisa adicionar uma coluna que é preenchida por padrão com um GUID usando a função NEWID(). Em seguida, você usa essa coluna no lugar da chave primária.

Você pode querer adicionar um índice a este campo, mas como as tabelas excluídas e inseridas em um gatilho estão na memória, elas podem não ser usadas e podem ter um efeito negativo no desempenho.