首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Server:通过泛型触发器记录数据库更改

Server:通过泛型触发器记录数据库更改
EN

Stack Overflow用户
提问于 2020-12-03 16:07:35
回答 1查看 514关注 0票数 0

本文创建一个触发器来记录数据库中的insert-、update-和delete-语句。开始,我想创建一个类似的触发器,它不需要定义表及其列。这将减少删除或添加列时的人为错误。

我遇到了许多问题(因此,与本文相比需要额外的代码),但无法克服通过触发器中的一个字符串名称访问表列的问题。

代码语言:javascript
运行
复制
-- Create the ChangeLog table
CREATE TABLE ChangeLog
(
    ID BIGINT PRIMARY KEY IDENTITY(1,1) NOT NULL,
    COMMAND NCHAR(6) NOT NULL,
    CHANGED_DATE DATETIME2 DEFAULT GETDATE() NOT NULL,
    TABLE_NAME NVARCHAR(255) NOT NULL,
    COLUMN_NAMES TEXT NULL,
    COLUMN_OLD_VALUES TEXT NULL,
    COLUMN_NEW_VALUES TEXT NULL,
    USERNAME NVARCHAR(100) NOT NULL
)
GO
代码语言:javascript
运行
复制
-- Create Trigger for Table to log changes
ALTER TRIGGER CHANGE_MyTableName
ON MyTableName
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
    -- Define which command was executed
    DECLARE @command CHAR(6)
    SET @command =
    CASE
        WHEN EXISTS(SELECT * FROM inserted) AND EXISTS(SELECT * FROM deleted) THEN 'UPDATE'
        WHEN EXISTS(SELECT * FROM inserted) THEN 'INSERT'
        WHEN EXISTS(SELECT * FROM deleted) THEN 'DELETE'
        ELSE NULL
    END

    -- Define variables
    DECLARE @seperator NVARCHAR(2)
    SET @seperator = ', '

    DECLARE @column_name NVARCHAR(255)
    DECLARE @column_names VARCHAR(MAX)
    DECLARE @column_old_values VARCHAR(MAX)
    DECLARE @column_new_values VARCHAR(MAX)

    -- Select the column names to populate @column_names separated by ', '
    SELECT @column_names = COALESCE(@column_names + @seperator, '') + COLUMN_NAME 
    FROM information_schema.columns 
    WHERE table_name = 'MyTableName'

    -- Create cursor to populate @column_old_values or/and @column_new_values
    DECLARE CURSOR_FOR_COLUMN_NAMES CURSOR FOR 
        -- Select the column name as a string
        SELECT sys.columns.name AS ColumnName
        FROM sys.columns JOIN sys.tables ON sys.columns.object_id = tables.object_id
        WHERE tables.name = 'MyTableName'

    -- Perform the first fetch.
    OPEN CURSOR_FOR_COLUMN_NAMES 

    FETCH NEXT FROM CURSOR_FOR_COLUMN_NAMES INTO @column_name

    WHILE @@FETCH_STATUS = 0
    BEGIN
        IF @command = 'UPDATE' or @command = 'DELETE'
            -- Select the old values to populate @column_old_values separated by ', '
            SELECT @column_old_values = COALESCE(@column_old_values + @seperator, '') + @column_name 
            FROM deleted

        IF @command = 'UPDATE' or @command = 'INSERT'
            -- Select the new values to populate @column_new_values separated by ', '
            SELECT @column_new_values = COALESCE(@column_new_values + @seperator, '') + @column_name 
            FROM inserted

        FETCH NEXT FROM CURSOR_FOR_COLUMN_NAMES INTO @column_name
    END

    CLOSE CURSOR_FOR_COLUMN_NAMES
    DEALLOCATE CURSOR_FOR_COLUMN_NAMES

    -- Insert into the ChangeLog table
    IF @command = 'UPDATE'
        INSERT INTO ChangeLog (COMMAND, CHANGED_DATE, TABLE_NAME, COLUMN_NAMES, COLUMN_OLD_VALUES, COLUMN_NEW_VALUES, USERNAME)
        VALUES (@command, GETDATE(), 'MyTableName', @column_names, @column_old_values, @column_new_values, USER_NAME())

    IF @command = 'INSERT'
        INSERT INTO ChangeLog (COMMAND, CHANGED_DATE, TABLE_NAME, COLUMN_NAMES, COLUMN_NEW_VALUES, USERNAME)
        VALUES (@command, GETDATE(), 'MyTableName', @column_names, @column_new_values, USER_NAME())

    IF @command = 'DELETE'
        INSERT INTO ChangeLog (COMMAND, CHANGED_DATE, TABLE_NAME, COLUMN_NAMES, COLUMN_OLD_VALUES, USERNAME)
        VALUES (@command, GETDATE(), 'MyTableName', @column_names, @column_old_values, USER_NAME())
END
GO

当前代码为COLUMN_NAMESCOLUMN_OLD_VALUESCOLUMN_NEW_VALUES创建了一个具有相同值的行,因为填充@column_old_values@column_new_values的代码实际上执行的是SELECT 'column_name' FROM AnyTable而不是SELECT columnn_name FROM MyTableName,但我似乎不知道如何解决这个问题。

我尝试过使用EXEC()命令,但这似乎部分地结束了我的触发器?即使代码已经编译,我仍然会收到一个关于未关闭引号的错误。

Msg 102,15级,状态1,第16线 在“=”附近不正确的语法。 Msg 105,15级,状态1,第16行 字符串') +已删除的ColumnName1‘之后的未闭合引号。

我认为只要这个问题解决了,这个通用触发器就能工作,但当然,我也很乐意使用其他的解决方案。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-12-03 22:09:16

当前代码中的一些错误包括:

  • 当0行受到影响时出现错误,因为所有表都是空的,但您没有处理空命令,而且当试图将空命令插入ChangeLog时,它会生成一个错误。
  • 您的游标将以一种奇怪的方式将所有受影响的行串在一起;即使使其工作,如果4行受到影响,则在ChangeLog中将有1行,其中column_old_values将保存类似的内容(col1、col2、col2)。
  • 您的游标需要动态SQL才能使用动态列名,但与代码相比,动态SQL处于不同的作用域,因此需要复制inserteddeleted触发器作用域表以使用动态SQL。
  • 动态SQL试图使用不同作用域中不存在的变量。如果将动态SQL放入字符串中,则调试动态SQL要容易得多,然后在尝试EXEC之前打印该字符串以供审阅。

编辑:

这个选项如何呢?它不依赖于了解列,而是依赖于事先了解表PK?这些变化不应该像其他列那样频繁地发生变化,而且性能要远远优于您想要做的事情。这是我在一个表上实现的一个示例,我们不确定我们的几十个用户中的一个还在使用它,我需要在一年多的时间内跟踪它。

代码语言:javascript
运行
复制
-- Create Trigger for Table to log changes
ALTER TRIGGER AUDIT_MyTableName
ON bookings
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
    SET NOCOUNT ON;

    -- Grab trx type
    DECLARE @command char(6) 
    SET @command =
    CASE
        WHEN EXISTS(SELECT 1 FROM inserted) AND EXISTS(SELECT 1 FROM deleted) THEN 'UPDATE'
        WHEN EXISTS(SELECT 1 FROM inserted) THEN 'INSERT'
        WHEN EXISTS(SELECT 1 FROM deleted) THEN 'DELETE'
        ELSE '0 ROWS' -- if no rows affected, trigger does NOT record an entry
    END 
 
    IF @command = 'INSERT'

        -- Add audit entry
        INSERT INTO ChangeLog (COMMAND, CHANGED_DATE, TABLE_NAME, /*COLUMN_NAMES,*/ COLUMN_OLD_VALUES, COLUMN_NEW_VALUES, USERNAME)
        SELECT 
            Command     = @command, 
            ChangeDate  = GETDATE(), 
            TableName   = 'bookings', 
            --ColNames  = @column_names, 
            Column_OLD_Values   = NULL, 
            Column_NEW_Values   = (SELECT inserted.* for xml path('')), 
            Username    = SUSER_SNAME()
        FROM inserted 
    
    ELSE IF @command = 'DELETE'

        -- Add audit entry
        INSERT INTO ChangeLog (COMMAND, CHANGED_DATE, TABLE_NAME, /*COLUMN_NAMES,*/ COLUMN_OLD_VALUES, COLUMN_NEW_VALUES, USERNAME)
        SELECT 
            Command     = @command, 
            ChangeDate  = GETDATE(), 
            TableName   = 'bookings', 
            --ColNames  = @column_names, 
            Column_OLD_Values   = (SELECT deleted.* for xml path('')), 
            Column_NEW_Values   = NULL,
            Username    = SUSER_SNAME()
        FROM deleted

    ELSE -- is UPDATE 

        -- Add audit entry
        INSERT INTO ChangeLog (COMMAND, CHANGED_DATE, TABLE_NAME, /*COLUMN_NAMES,*/ COLUMN_OLD_VALUES, COLUMN_NEW_VALUES, USERNAME)
        SELECT 
            Command     = @command, 
            ChangeDate  = GETDATE(), 
            TableName   = 'bookings', 
            --ColNames  = @column_names, 
            Column_OLD_Values   = (SELECT deleted.* for xml path('')), 
            Column_NEW_Values   = (SELECT inserted.* for xml path('')), 
            Username    = SUSER_SNAME()
        FROM inserted 
        INNER JOIN deleted ON inserted.bookingID = deleted.bookingID -- join on w/e the PK is
END

其结果完全适用于您需要的任何东西:

如果您愿意将COLUMN_OLD_VALUESCOLUMN_NEW_VALUES的列类型更改为XML,则只需在每个for xml path('')之后添加, type,并且XML在SSMS中是可单击的和易于阅读的。

代码语言:javascript
运行
复制
Column_OLD_Values   = (SELECT deleted.* for xml path(''), type), 
Column_NEW_Values   = (SELECT inserted.* for xml path(''), type), 
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65129660

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档