【问题标题】:Field level audit on SQL Server using Trigger使用触发器在 SQL Server 上进行字段级审计
【发布时间】:2014-05-07 07:17:49
【问题描述】:

我正在尝试编写一个触发器来审核表的每个字段——表中一行的旧值和新值。如果任何字段已被修改,我需要将字段旧值和新值连同字段名称一起保存在审计表中,作为新条目。

create trigger Trg_Institution_FieldAudit on Table1 AFTER UPDATE AS
DECLARE @OldName VARCHAR(30)
DECLARE @CurrentName VARCHAR(30)
DECLARE @OldId VARCHAR(30)
DECLARE @CurrentId VARCHAR(30)
DECLARE @modifiedBy VARCHAR(30)
If update(Name)
  BEGIN
    select @OldName = Name from deleted
    select @CurrentName = Name from Inserted
    select @OldId = ID from deleted
    select @currentId = ID from Inserted
    select @modifiedBy = modifiedBy from deleted
    --INSERT statement for Name field alone
    END;

这适用于少数字段,但我有很多字段(超过 60 个),并且由于很多 if 条件,我没有达到所需的性能。有没有更好的方法来做到这一点?最重要的是,该表中大约有 300 万条记录发生并发更新,这使得很多事情出错:(

编辑:UPDATE 语句只会更新一行

【问题讨论】:

  • 您的触发器有 MAJOR 缺陷,因为您似乎认为它会每行调用一次 - 那是不是 b> 情况。触发器将每个语句触发一次,因此如果您的 UPDATE 语句影响 25 行,您将触发触发器一次,然后是 Inserted 和 @987654324 @ 每个将包含 25 行。您的代码将在这 25 行中选择哪一行? select @OldName = Name from deleted - 它是不确定的 - 它会选择一个 - 并忽略所有其他行。您需要重写触发器以考虑到这一点!
  • 好的,我明白了。谢谢你。如果我们认为更新语句总是一次发生在一行上,即 1 行的 1 个 UPDATE 语句,那么检查所有已更新的列并在审计表中创建单独条目的理想方法是什么?跨度>

标签: sql-server database triggers


【解决方案1】:

哦,天哪。请尽可能避免使用光标!您可以轻松地将插入语句与引用插入和删除表的选择一起使用。下面是我的一个更新触发器的示例。

DECLARE @AuditTime DATETIME
SET @AuditTime = GetDate()

IF UPDATE([AccountManager])

    INSERT INTO Audit.AuditHistory (AuditId, AuditDate, AuditTableName,  EntityKey, AuditFieldName, OldValue, NewValue, FieldDisplayText, OldDisplayText, NewDisplayText, ModifiedBy)
    SELECT NewId(),
           @AuditTime,
           '[tblOpportunity]',
           cast(d.[GOTSID] AS varchar),
           '[AccountManager]',
           cast(d.[AccountManager] AS varchar(250)),
           cast(i.[AccountManager] AS varchar(250)),
           'Account Manager',
           isnull(cast(d.[AccountManager] AS varchar(250)), ''),
           isnull(cast(i.[AccountManager] AS varchar(250)), ''),
           isnull(i.[ModifiedBy], '')
    FROM deleted d
    INNER JOIN inserted i ON d.GOTSID = i.GOTSID 
    WHERE d.[AccountManager] <> i.[AccountManager]
    OR (d.[AccountManager] IS NOT NULL
        AND i.AccountManager IS NULL)
    OR (d.[AccountManager] IS NULL
        AND i.AccountManager IS NOT NULL)

【讨论】:

    【解决方案2】:

    @marc_s 是对的,您必须重新构建触发器和表。这里举个例子。

    您需要将 where 条件放入 select @OldName = Name from deleted。

    例如-

    **

    CREATE TRIGGER Trg_Institution_FieldAudit ON Table1 FOR UPDATE 
    AS 
    DECLARE @OldName VARCHAR(30) 
    DECLARE @CurrentName VARCHAR(30)
    IF UPDATE (Name) 
    BEGIN
           SET @OldName = Table1.Name     FROM deleted
               WHERE Table1.Name = deleted.Name;        
       SET @CurrentName = Table1.Name     FROM inserted                    
               WHERE Table1.Name = inserted.Name ; 
     --INSERT statement for old and new values.         
    END 
    GO**
    

    【讨论】:

    • 操作使用的是 SQL Server,而不是 Oracle(这是给 Oracle 的,对吧?)
    • sql server 中似乎没有 for each :(
    • 只是想知道这与我问的有什么不同:(
    【解决方案3】:

    在 SQL Server 中寻找 FOR EACH 的替代方案后,我发现可以使用 CURSOR。它可以达到目的,但需要有人来验证。

    CREATE TRIGGER Trg_Institution_FieldAudit_1 ON dbo.Institution FOR UPDATE as 
    
    -- DECLARE Variables
    
    DECLARE institution_cursor CURSOR DYNAMIC FOR SELECT * FROM DELETED
    OPEN institution_cursor FETCH NEXT FROM institution_cursor INTO -- @variables here
    WHILE (@@FETCH_STATUS = 0)
        BEGIN    
            IF UPDATE(COL1)
            BEGIN
                INSERT INTO AuditTable VALUES (COL1, @prev, @next);
            END;
    
            FETCH NEXT FROM institution_cursor INTO -- @Variables here
        END
    CLOSE institution_cursor
    DEALLOCATE institution_cursor
    

    【讨论】:

      猜你喜欢
      • 2011-12-02
      • 2020-11-07
      • 1970-01-01
      • 1970-01-01
      • 2016-09-18
      • 1970-01-01
      • 2013-02-13
      • 2014-05-04
      • 1970-01-01
      相关资源
      最近更新 更多