T-SQL 触发器 - 审计列更改
T-SQL Trigger - Audit Column Change
给定一个带有 ID 的简单 table,审计正在更改的列的正确方法是什么。我在查看各种似乎不起作用的答案后问。
这是我的:
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
但是这个returns没有结果。
为了获得实际更改的列的结果,我需要 运行 一个非常昂贵的查询:
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(即使已设置)在设置为 previous/old 值时未被审计。
update(ColumnName)
并不意味着值已更改,只是该列涉及 insert/update - 并且它将始终涉及插入。您需要使用 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;
显然您需要调整该逻辑来处理您正在审核的所有列。
给定一个带有 ID 的简单 table,审计正在更改的列的正确方法是什么。我在查看各种似乎不起作用的答案后问。
这是我的:
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
但是这个returns没有结果。
为了获得实际更改的列的结果,我需要 运行 一个非常昂贵的查询:
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(即使已设置)在设置为 previous/old 值时未被审计。
update(ColumnName)
并不意味着值已更改,只是该列涉及 insert/update - 并且它将始终涉及插入。您需要使用 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;
显然您需要调整该逻辑来处理您正在审核的所有列。