SQL Server auto audit updated column

后端 未结 2 1082
無奈伤痛
無奈伤痛 2021-01-07 08:56

I need to create a trigger on SQL Server to write only updated column into an audit table with table name, column name, previous/new value, date, time and user for specific

相关标签:
2条回答
  • 2021-01-07 09:26

    i normally have a table like this to store the audit data and i got this solution from here https://www.simple-talk.com/sql/database-administration/pop-rivetts-sql-server-faq-no.5-pop-on-the-audit-trail/

        CREATE TABLE [dbo].[Audit](
        [AuditID] [int] IDENTITY(1,1) NOT NULL,
        [Type] [char](1) NULL,
        [TableName] [varchar](128) NULL,
        [PK] [varchar](1000) NULL,
        [FieldName] [varchar](128) NULL,
        [OldValue] [varchar](1000) NULL,
        [NewValue] [varchar](1000) NULL,
        [UpdateDate] [datetime] NULL,
        [UserName] [varchar](128) NULL,
     CONSTRAINT [PK_Audit] PRIMARY KEY CLUSTERED 
    (
        [AuditID] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    

    And then add the below trigger to every table i want to audit

    CREATE TRIGGER [dbo].[Tr_DB_Audit] ON [dbo].['YourTableName'] FOR INSERT, UPDATE, DELETE
    AS
    
    DECLARE @bit INT ,
           @field INT ,
           @maxfield INT ,
           @char INT ,
           @fieldname VARCHAR(128) ,
           @TableName VARCHAR(128) ,
           @PKCols VARCHAR(1000) ,
           @sql VARCHAR(2000), 
           @UpdateDate VARCHAR(21) ,
           @UserName VARCHAR(128) ,
           @Type CHAR(1) ,
           @PKSelect VARCHAR(1000)
    
    
    --You will need to change @TableName to match the table to be audited
    SELECT @TableName = 'NameOfTableYouWantToAudit'
    
    -- date and user
    SELECT         @UserName = SYSTEM_USER ,
           @UpdateDate = CONVERT(VARCHAR(8), GETDATE(), 112) 
                   + ' ' + CONVERT(VARCHAR(12), GETDATE(), 114)
    
    -- Action
    IF EXISTS (SELECT * FROM inserted)
           IF EXISTS (SELECT * FROM deleted)
                   SELECT @Type = 'U'
           ELSE
                   SELECT @Type = 'I'
    ELSE
           SELECT @Type = 'D'
    
    -- get list of columns
    SELECT * INTO #ins FROM inserted
    SELECT * INTO #del FROM deleted
    
    -- Get primary key columns for full outer join
    SELECT @PKCols = COALESCE(@PKCols + ' and', ' on') 
                   + ' i.' + c.COLUMN_NAME + ' = d.' + c.COLUMN_NAME
           FROM    INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
    
                  INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
           WHERE   pk.TABLE_NAME = @TableName
           AND     CONSTRAINT_TYPE = 'PRIMARY KEY'
           AND     c.TABLE_NAME = pk.TABLE_NAME
           AND     c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
    
    -- Get primary key select for insert
    SELECT @PKSelect = COALESCE(@PKSelect+'+','') 
           + '''<' + COLUMN_NAME 
           + '=''+convert(varchar(100),
    coalesce(i.' + COLUMN_NAME +',d.' + COLUMN_NAME + '))+''>''' 
           FROM    INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
                   INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
           WHERE   pk.TABLE_NAME = @TableName
           AND     CONSTRAINT_TYPE = 'PRIMARY KEY'
           AND     c.TABLE_NAME = pk.TABLE_NAME
           AND     c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
    
    IF @PKCols IS NULL
    BEGIN
           RAISERROR('no PK on table %s', 16, -1, @TableName)
           RETURN
    END
    
    SELECT         @field = 0, 
           @maxfield = MAX(ORDINAL_POSITION) 
           FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TableName
    WHILE @field < @maxfield
    BEGIN
           SELECT @field = MIN(ORDINAL_POSITION) 
                   FROM INFORMATION_SCHEMA.COLUMNS 
                   WHERE TABLE_NAME = @TableName 
                   AND ORDINAL_POSITION > @field
           SELECT @bit = (@field - 1 )% 8 + 1
           SELECT @bit = POWER(2,@bit - 1)
           SELECT @char = ((@field - 1) / 8) + 1
           IF SUBSTRING(COLUMNS_UPDATED(),@char, 1) & @bit > 0
                                           OR @Type IN ('I','D')
           BEGIN
                   SELECT @fieldname = COLUMN_NAME 
                           FROM INFORMATION_SCHEMA.COLUMNS 
                           WHERE TABLE_NAME = @TableName 
                           AND ORDINAL_POSITION = @field
                   SELECT @sql = '
    insert Audit (    Type, 
                   TableName, 
                   PK, 
                   FieldName, 
                   OldValue, 
                   NewValue, 
                   UpdateDate, 
                   UserName)
    select ''' + @Type + ''',''' 
           + @TableName + ''',' + @PKSelect
           + ',''' + @fieldname + ''''
           + ',convert(varchar(1000),d.' + @fieldname + ')'
           + ',convert(varchar(1000),i.' + @fieldname + ')'
           + ',''' + @UpdateDate + ''''
           + ',''' + @UserName + ''''
           + ' from #ins i full outer join #del d'
           + @PKCols
           + ' where i.' + @fieldname + ' <> d.' + @fieldname 
           + ' or (i.' + @fieldname + ' is null and  d.'
                                    + @fieldname
                                    + ' is not null)' 
           + ' or (i.' + @fieldname + ' is not null and  d.' 
                                    + @fieldname
                                    + ' is null)' 
                   EXEC (@sql)
           END
    END
    

    You could also have a look at this article http://weblogs.asp.net/jgalloway/archive/2008/01/27/adding-simple-trigger-based-auditing-to-your-sql-server-database.aspx

    0 讨论(0)
  • 2021-01-07 09:36

    We’re using third party tool ApexSQL Audit to generate audit triggers since we have a lot of tables that require auditing.

    If you don’t need a third party tool you can use this in trial mode and see how they are implementing triggers and storage tables.

    Below is a sample trigger and storage tables I picked up quickly from my database.

    Data storage table:

    enter image description here

    Table that stores details about transactions:

    enter image description here

    Here is a sample of delete trigger. Note the comment inline

    CREATE TRIGGER [dbo].[tr_d_AUDIT_Table_Name]
    ON [dbo].[Table_Name]
    FOR DELETE
    NOT FOR REPLICATION
    AS
    
    BEGIN
        DECLARE 
            @IDENTITY_SAVE              varchar(50),
            @AUDIT_LOG_TRANSACTION_ID       Int,
            @PRIM_KEY               nvarchar(4000),
            @ROWS_COUNT             int
    
        SET NOCOUNT ON
    
    
        Select @ROWS_COUNT=count(*) from deleted
        Set @IDENTITY_SAVE = CAST(IsNull(@@IDENTITY,1) AS varchar(50))
    
        INSERT
        INTO dbo.AUDIT_LOG_TRANSACTIONS
        (
            TABLE_NAME,
            TABLE_SCHEMA,
            AUDIT_ACTION_ID,
            HOST_NAME,
            APP_NAME,
            MODIFIED_BY,
            MODIFIED_DATE,
            AFFECTED_ROWS,
            [DATABASE]
        )
        values(
            'Table_Name',
            'dbo',
            3,  
            CASE 
              WHEN LEN(HOST_NAME()) < 1 THEN ' '
              ELSE HOST_NAME()
            END,
            CASE 
              WHEN LEN(APP_NAME()) < 1 THEN ' '
              ELSE APP_NAME()
            END,
            SUSER_SNAME(),
            GETDATE(),
            @ROWS_COUNT,
            'database_name'
        )
    
        Set @AUDIT_LOG_TRANSACTION_ID = SCOPE_IDENTITY()
    
        INSERT
        INTO dbo.AUDIT_LOG_DATA
        (
            AUDIT_LOG_TRANSACTION_ID,
            PRIMARY_KEY_DATA,
            COL_NAME,
            OLD_VALUE_LONG,
            DATA_TYPE
            , KEY1
        )
        SELECT
            @AUDIT_LOG_TRANSACTION_ID,
            convert(nvarchar(1500), IsNull('[Order_ID]='+CONVERT(nvarchar(4000), OLD.[Order_ID], 0), '[Order_ID] Is Null')),
            'Order_ID',
            CONVERT(nvarchar(4000), OLD.[Order_ID], 0),
            'A'
            ,  CONVERT(nvarchar(500), CONVERT(nvarchar(4000), OLD.[Order_ID], 0))
        FROM deleted OLD
        WHERE
            OLD.[Order_ID] Is Not Null
    
        /*
           Insert statement above is replicated for each column being audited
         */
    
    END  
    
    0 讨论(0)
提交回复
热议问题