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)
并不意味着该值已更改,只是该列参与了插入/更新 - 并且它将始终参与插入。您需要使用 inserted
和 deleted
比较旧值和新值,例如
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 触发器 - 审计列更改的主要内容,如果未能解决你的问题,请参考以下文章