【问题标题】:T-SQL Trigger - Audit Column ChangeT-SQL 触发器 - 审计列更改
【发布时间】:2021-08-04 06:28:23
【问题描述】:

给定一个带有 ID 的简单表,审计正在更改的列的正确方法是什么。我在照顾各种似乎不起作用的答案后问。

这是我所拥有的:

Create Table Tbl_Audit
(
    AuditId int identity(1,1) not null,
    Tbl_Id int not null.
    Tbl_Old_ColumnValue varchar(255),
    Tbl_New_ColumnValue varchar(255)
)

GO

Create Trigger Tr_Tbl_ColumnChanged on Tbl
after insert, update
As
begin
    if(update(ColumnName))
        begin
            insert into tbl_audit
            (
                Tbl_Id,
                Tbl_Old_ColumnName,
                Tbl_New_ColumnName
            )
            select
                tbl.PKId,
                tbl.ColumnName,
                i.ColumnName,
            from
                Tbl tbl join 
                inserted i
                on tbl.PKId = i.PKId
            
end

我看到的是数千个例子,Tbl_Old_ColumnValue = Tbl_New_ColumnValue ,这不是我想要的。

我希望能跑:

select top 10 * from tbl_audit where Tbl_Old_ColumnValue !=Tbl_New_ColumnValue 

但这不会返回任何结果。

为了获得实际更改的列的结果,我需要运行一个非常昂贵的查询:

select top 10 
old.AuditId,
old.Tbl_Old_ColumnValue,
new.Tbl_Old_ColumnValue  as [Tbl_New_ColumnValue] 
from tbl_audit [old]
join Tbl_Audit [new]
on [ol].Tbl_Id= [new].Tbl_Id and [old].AuditId != [new].AuditId
where [old].Tbl_Old_ColumnValue != [new].Tbl_Old_ColumnValue 

结果:

AuditId Tbl_Id  Tbl_Old_ColumnValue Tbl_New_ColumnValue
10051       1       old_value           old_value
10052       1       new_value           new_value

但这并没有达到我的预期:

AuditId Tbl_Id  Tbl_Old_ColumnValue Tbl_New_ColumnValue
10057       1   old_value           Some New Value

奇怪的是,如果我直接通过 SSMS 修改列:

update Tbl set Tbl.ColumnValue = 'Some New Value'

我看到了我对触发器的期望:

AuditId Tbl_Id  Tbl_Old_ColumnValue Tbl_New_ColumnValue
10057       1   old_value           Some New Value

我做错了什么?

另外,我如何消除对 update(ColumnName) 实际上是错误的行的审计。 IE,ColumnName(即使已设置)在设置为上一个/旧值时不会被审核。

【问题讨论】:

    标签: sql sql-server tsql triggers audit


    【解决方案1】:

    update(ColumnName) 并不意味着该值已更改,只是该列参与了插入/更新 - 并且它将始终参与插入。您需要使用 inserteddeleted 比较旧值和新值,例如

    insert into tbl_audit
    (
        Tbl_Id,
        Tbl_Old_ColumnName,
        Tbl_New_ColumnName
    )
    select
        tbl.PKId,
        tbl.ColumnName,
        i.ColumnName,
    from
        inserted i
        left join deleted d on d.PKId = i.PKId
        -- Insert d.PKId is null, there are no records in deleted
        where d.PKId is null
        -- Change from null to value
        or (i.ColumnName is null and d.ColumnName is not null)
        -- Change from value to null
        or (i.ColumnName is not null and d.ColumnName is null)
        -- Change in value
        or i.ColumnName <> d.ColumnName;
    

    您可以使用coalesce 和一个永远不会在您的数据中实际出现的合适值来简化空值检查。

    documentation 在这方面实际上非常出色。

    如果列不总是包含在更新中,那么update(ColumnName) 测试仍然值得做,因为它加快了触发器的速度,并且触发器应该尽可能快。就我个人而言,我很早就短路了,例如if not update(ColumnName) return;

    显然,您需要调整该逻辑来处理您正在审核的所有列。

    【讨论】:

    • 已删除...嗯? ```d.pkid 为空```有什么意义
    • 是的,该列可以为空
    • 是的,只要它被改变,所以是的,插入。所以 d.pkid is null 是用于插入。明白了。
    • IE,在它第一次从 null 设置为某个值之后,它永远不会再改变。所以我的审计还有很多其他的列SUSER_NAME(), GetDate(), APP_NAME(), Host_Name(), cast(CONNECTIONPROPERTY('client_net_address') as nvarchar(128)) 等等,我没有在我的问题中包括。
    • 我实际上是要编辑您的答案并放置文档链接。你打败了我。
    猜你喜欢
    • 2017-10-08
    • 1970-01-01
    • 2020-01-08
    • 2013-06-20
    • 1970-01-01
    • 2013-03-29
    • 1970-01-01
    • 1970-01-01
    • 2018-06-04
    相关资源
    最近更新 更多