如何在 SQL Server 触发器中复制插入、更新、删除的行

Posted

技术标签:

【中文标题】如何在 SQL Server 触发器中复制插入、更新、删除的行【英文标题】:How to copy an inserted,updated,deleted row in a SQL Server trigger(s) 【发布时间】:2011-05-22 10:35:12 【问题描述】:

如果用户更改表 HelloWorlds,那么我想要“他们执行的操作”、执行时间,并将原始行的副本插入到 HelloWorldsHistory

由于列的长度,我希望避免插入、更新和删除操作的单独触发器。

我试过了:

create trigger [HelloWorlds_After_IUD] on [HelloWorlds]
FOR insert, update, delete
as
if @@rowcount = 0 
return
if exists (select 1 from inserted) and not exists (select 1 from deleted)
begin
insert into HelloWorldHistory (hwh_action, ..long column list..)
select 'INSERT', helloWorld.id, helloWorld.text ... and more from inserted
end
else
    if exists (select 1 from inserted) and exists (select 1 from deleted)
    begin
insert into HelloWorldHistory (hwh_action, ..long column list..)
select 'UPDATE', helloWorld.id, helloWorld.text ... and more from deleted 
    end
    else
    begin
insert into HelloWorldHistory (hwh_action, ..long column list..)
select 'DELETE', helloWorld.id, helloWorld.text ... and more from deleted
    end
end

我从未见过插入出现,但我看到了更新。我将尝试 3 个单独的触发器,但维护列列表不会很有趣。

【问题讨论】:

不要使用 DELETED 作为 UPDATE 的数据源,您只会记录之前已经记录的值。您的日志行将是“更新”类型,但数据可能来自插入。在我的回答中看到我的评论。 【参考方案1】:

试试这样的:

CREATE TRIGGER YourTrigger ON YourTable
   AFTER INSERT,UPDATE,DELETE
AS

DECLARE @HistoryType    char(1) --"I"=insert, "U"=update, "D"=delete

SET @HistoryType=NULL

IF EXISTS (SELECT * FROM INSERTED)
BEGIN
    IF EXISTS (SELECT * FROM DELETED)
    BEGIN
        --UPDATE
        SET @HistoryType='U'
    END
    ELSE
    BEGIN
        --INSERT
        SET @HistoryType='I'
    END
    --handle insert or update data
    INSERT INTO YourLog
            (ActionType,ActionDate,.....)
        SELECT
            @HistoryType,GETDATE(),.....
            FROM INSERTED

END
ELSE IF EXISTS(SELECT * FROM DELETED)
BEGIN
    --DELETE
    SET @HistoryType='D'

    --handle delete data, insert into both the history and the log tables
    INSERT INTO YourLog
            (ActionType,ActionDate,.....)
        SELECT
            @HistoryType,GETDATE(),.....
            FROM DELETED

END
--ELSE
--BEGIN
--    both INSERTED and DELETED are empty, no rows affected
--END

【讨论】:

效果很好。在 Oracle 工作了几年之后,我对挖掘自己尘土飞扬的 MS SQL 技能感觉好很多。 我的最终解决方案将是这个“架构”,在源表和临时事务触发器表之间检查 bob 的 pk 列。此外,我将从已删除的表中提取“更新”数据,而不是插入。 我强烈建议您从 INSERTED 表中记录 UPDATE。这是一个示例:插入行数据=AAA(INSERTED=AAA 进入日志),将同一行更新为 BBB(INSERTED=BBB,DELETED=AAA,我说将 BBB 发送到日志,而不是 AAA,这已经记录了)。此外,当使用IF EXISTS 时,所有“密钥检查”都不是必需的,只是开销。触发器一次只能触发 1 个命令,即 INSERT 或 UPDATE 或 DELETE,因此无需匹配键来确定操作。您永远不会在触发器中将 UPDATEs 数据与 INSERTs 数据混合。 很好的信息。将更新与插入混合在一起正是我所担心的。 @KM 这非常优雅,因为它的简单性和对三个语句的原子性的洞察消除了 PK 匹配的需要。我什至用 'MERGE' 语句对此进行了测试,该语句似乎在一批中混合了操作,但实际上并没有,而且这个逻辑很好。【参考方案2】:

您需要关联(匹配)插入和删除列中的行。这样的事情应该会更好。

create trigger [HelloWorlds_After_IUD] on [HelloWorlds]
FOR insert, update, delete
as

insert into HeloWorldsHistory
select 'INSERT', helloWorld.id, helloWorld.text ... and more 
from inserted
where myKeyColumn not in (select myKeyColumn from deleted)

insert into HeloWorldsHistory
select 'DELETE', helloWorld.id, helloWorld.text ... and more 
from deleted
where myKeyColumn not in (select myKeyColumn from inserted)

insert into HeloWorldsHistory
select 'UPDATE', helloWorld.id, helloWorld.text ... and more 
from inserted
where myKeyColumn in (select myKeyColumn from deleted)

【讨论】:

我一定会在pk上匹配!谢谢你的收获! 查看KM对这种情况下PK检查的评论

以上是关于如何在 SQL Server 触发器中复制插入、更新、删除的行的主要内容,如果未能解决你的问题,请参考以下文章

如何:将实际执行方法从“行”更改为“批处理”-Azure SQL Server

如何在插入时在 SQL Server 中创建触发器?

如何在 SQL Server 2008 上创建插入更新触发器

SQL Server 触发器在插入表之前验证数据

帮助 SQL Server 触发器在插入前截断错误数据

插入和更新后未触发 SQL Server 触发器