T-SQL 触发器 - 审计列更改

Posted

技术标签:

【中文标题】T-SQL 触发器 - 审计列更改【英文标题】:T-SQL Trigger - Audit Column Change 【发布时间】: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(即使已设置)在设置为上一个/旧值时不会被审核。

【问题讨论】:

【参考方案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)) 等等,我没有在我的问题中包括。 我实际上是要编辑您的答案并放置文档链接。你打败了我。

以上是关于T-SQL 触发器 - 审计列更改的主要内容,如果未能解决你的问题,请参考以下文章

如果 Column_X 更新,PLSQL 触发器记录对审计表的更新

在 Mysql 中添加新列时如何轻松维护审计触发器

为 MySQL 表实现审计

在 Oracle 中审计 DML 更改

对审计列使用触发器

列值更改的 T-SQL (SQL Server 2016) 触发器,在插入