Search code examples
sqlsql-serversql-server-2014-express

After_Update trigger logging


USE [MY_DATABASE_NAME]
GO
/****** Object:  Trigger [dbo].[trg_After_Update]    Script Date: 16.12.2014 23:13:53 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[trg_After_Update]
ON [dbo].[MY_TABLE_NAME] 
FOR UPDATE
AS
    declare  @FOR_DATE date;
    declare @WRITTEN_ON smalldatetime;
    declare @WRITTEN_BY_WHO NVARCHAR(50); 
    declare @REPORT nvarchar(max);
    declare @HANDLED bit; 
    declare @HANDLED_BY NVARCHAR(50);
    declare @HANDLED_WHEN datetime;
    declare @COMMENT nvarchar(max);
    declare @AUDIT_ACTION NVARCHAR(50);
    declare @AUDIT_TIMESTAMP smalldatetime;


    select @FOR_DATE     = i.FOR_DATE from inserted i;  
    select @WRITTEN_ON = i.WRITTEN_ON from inserted i;
    select @WRITTEN_BY_WHO = i.WRITTEN_BY_WHO from inserted i;
    select @REPORT         = i.REPORT from inserted i;
    select @HANDLED      = i.HANDLED from inserted i;
    select @HANDLED_BY = i.HANDLED_BY from inserted i;
    select @HANDLED_WHEN = i.HANDLED_WHEN from inserted i;
    select @ COMMENT       = i.COMMENT from inserted i;

                   if update(REPORT)
        set @audit_action='Report change';
        if update(COMMENT)
        set @audit_action='Comment change';
        if update(HANDLED)
        set @audit_action='Handled change';

                        insert into AUDIT_MY_TABLE_NAME


(FOR_DATE,WRITTEN_ON,WRITTEN_BY_WHO,REPORT,HANDLED,HANDLED_BY,HANDLED_WHEN,COMMENT,USER,AUDIT_ACTION,AUDIT_TIMESTAMP) 
    values

(@FOR_DATE,@WRITTEN_ON,@WRITTEN_BY_WHO,@REPORT,@HANDLED,@HANDLED_BY,@HANDLED_WHEN,@COMMENT,USER_NAME(USER_ID()),@audit_action,getdate());

This trigger works more or less as expected. It logs any change to the 3 monitored fields. However, a insert of a new record into this table 'MY_TABLE_NAME' fires the above trigger. Then when I go and see the auditing table 'AUDIT_MY_TABLE_NAME' I see that this new record has been added there. The only difference is that the 'audit_action' field is empty. This insertion into the auditing table is probably caused by another trigger that updates 2 fields in 'MY_TABLE_NAME' after_insert.

My question would be this : I kind of like the way this trigger works. The only additional feature I would like added to it is that the 'audit_action' reads 'New record' instead of displaying empty now. Mind you,I am not logging new records but since this After_Update trigger logs them anyway,why not…. So what must I change in this 'after_update' trigger so that when an insert of new record occurs I have 'audit_action' reading 'New record' in my auditing table?


Solution

  • Be very careful when you have triggers written for single updates, but don't prevent logging for batch updates. If you update 20 records in a batch, you're going to get 1 insert into your audit table, which will be a random one of the 20.

    To meet your conditions without changing much, you can modify your trigger like so (this handles batch updates):

    ALTER TRIGGER [dbo].[trg_After_Update]
    ON [dbo].[MY_TABLE_NAME] 
    FOR UPDATE
    AS
        INSERT INTO AUDIT_MY_TABLE_NAME (FOR_DATE,WRITTEN_ON,WRITTEN_BY_WHO,REPORT,HANDLED,HANDLED_BY,HANDLED_WHEN,COMMENT,USER,AUDIT_ACTION,AUDIT_TIMESTAMP)
        SELECT i.FOR_DATE, i.WRITTEN_ON, i.WRITTEN_BY_WHO, i.REPORT, i.HANDLED, i.HANDLED_BY, i.HANDLED_WHEN, i.COMMENT, USER_NAME(USER_ID()),
            CASE  -- case statement is in reverse order to match your logic (bottom wins)
                WHEN i.HANDLED <> d.HANDLED THEN 'Handled Changed'
                WHEN i.COMMENT <> d.COMMENT THEN 'Comment Change'
                WHEN i.REPORT <> d.REPORT THEN 'Report Change'
                ELSE 'New Record'
            END,
            GETDATE()
        FROM inserted i
        LEFT JOIN deleted d ON i.pk = d.pk  -- join on your Primary Key that doesn't change
    END
    

    But I'm wondering if what you think is going on is correct. When you update more than 1 of those 3 fields all of that logic is going to run. If more than 1 column of your 3 is being updated, the last one wins. My guess is that your "New Record" update is really a field outside of your 3 UPDATE columns being updated.

    Here's another option and I'll let you choose what you think is best:

    ALTER TRIGGER [dbo].[trg_After_Update]
    ON [dbo].[MY_TABLE_NAME] 
    FOR UPDATE
    AS
        INSERT INTO AUDIT_MY_TABLE_NAME (FOR_DATE,WRITTEN_ON,WRITTEN_BY_WHO,REPORT,HANDLED,HANDLED_BY,HANDLED_WHEN,COMMENT,USER,AUDIT_ACTION,AUDIT_TIMESTAMP)
        SELECT i.FOR_DATE, i.WRITTEN_ON, i.WRITTEN_BY_WHO, i.REPORT, i.HANDLED, i.HANDLED_BY, i.HANDLED_WHEN, i.COMMENT, USER_NAME(USER_ID()),
            CASE WHEN i.pk IS NOT NULL AND d.pk IS NULL THEN 'New Record' ELSE
                CASE WHEN i.HANDLED <> d.HANDLED THEN 'Handled Changed. ' ELSE '' END + 
                CASE WHEN i.COMMENT <> d.COMMENT THEN 'Comment Change. ' ELSE '' END + 
                CASE WHEN i.REPORT <> d.REPORT THEN 'Report Change. ' ELSE '' END + 
                CASE WHEN i.HANDLED = d.HANDLED AND i.COMMENT = d.COMMENT AND i.REPORT = d.REPORT THEN 'Other Change.' ELSE '' END
            END,
            GETDATE()
        FROM inserted i
        LEFT JOIN deleted d ON i.pk = d.pk  -- join on your Primary Key that doesn't change
    END