【问题标题】:Trigger for UPDATE runs many time on batch UpdatesUPDATE 触发器在批量更新上运行多次
【发布时间】:2020-07-11 09:20:22
【问题描述】:

我所有的表都有一个用于 CRUD 操作的触发器。 这是一个示例:

ALTER TRIGGER [dbo].[Cities_tr] ON [dbo].[Cities] AFTER INSERT, UPDATE
AS
BEGIN 
    DECLARE @operation CHAR(6)

    SET @operation = CASE WHEN EXISTS (SELECT * FROM inserted) AND EXISTS (SELECT * FROM deleted)
        THEN 'Update'
        WHEN EXISTS (SELECT * FROM inserted)
        THEN 'Insert'     
        WHEN EXISTS(SELECT * FROM deleted)
        THEN 'Delete'
        ELSE NULL
        END 
    IF @operation = 'Insert'
        INSERT INTO history ([dt],[tname],[cuser] ,[id],op) 
            SELECT  GETDATE(),'Cities',  i.ldu, i.CityId,@operation
            FROM inserted i

    set nocount on

    IF @operation = 'Update'
        INSERT INTO history ([dt],[tname],[cuser] ,[id],op)   
            SELECT  GETDATE(),'Cities',  i.ldu,  i.CityId,@operation   
            FROM deleted d, inserted i
END 

如果我更新一行,一切正常,触发器会在历史记录中插入一行。

例如

update top(1) cities set f=1

但如果更新了不止一行,updatedrow^2 行将被插入。

例如 9 表示 3 行 100 表示 10 行...

我的触发器出了什么问题,我该如何解决?

【问题讨论】:

标签: sql sql-server sql-update sql-insert database-trigger


【解决方案1】:

您的代码的问题是您正在交叉加入inserted 和deleted。在多行更新时,两者都包含许多行,这些行是笛卡尔积相乘的。

看起来您实际上想要记录“新”行(插入或更新)。如果是这样,您不想从deleted 中进行选择。此外,条件逻辑可以在单个查询中移动,这样可以简化您的代码,如下所示:

ALTER TRIGGER dbo.Cities_tr
    ON dbo.Cities
    AFTER INSERT, UPDATE  
AS
BEGIN
    INSERT INTO history (dt, tname, cuser, id, op)
    SELECT 
        getdate(),
        'Cities',
        ldu,
        cityId,
        case when exists (select 1 from deleted) then 'Update' else 'Insert' end
    FROM inserted;
END

另一方面,如果您想要同时记录“旧”和“新”行(这不是您的代码所做的,即使是在单行更新时),那么您想要 union all 两个查询从inserted 和deleted 中选择。

【讨论】:

    【解决方案2】:

    您正在交叉加入 inserted 和 deleted。通常,它们将使用表的主键进行连接,大概是CityId:

        INSERT INTO history ([dt], [tname], [cuser] , [id], op)   
            SELECT  GETDATE(), 'Cities',  i.ldu,  i.CityId, @operation   
            FROM deleted d JOIN
                 inserted i
                 ON d.CityId = i.CityId;
    

    在这种情况下,deleted 没有被使用,因此它甚至不需要包含在查询中。

    您可以使用LEFT JOIN 将整个触发器实现为表中的单个查询:

        INSERT INTO history ([dt], [tname], [cuser] , [id], op)   
            SELECT GETDATE(), 'Cities',  i.ldu,  i.CityId,
                   (CASE WHEN d.CityId IS NOT NULL THEN 'Update' ELSE 'Insert' END)  
            FROM inserted i LEFT JOIN
                 deleted d                 
                 ON d.CityId = i.CityId;
    

    【讨论】:

    • 谢谢。所有这些表的触发器都是由我编写的脚本创建的。看起来我对这个加入错误视而不见!
    猜你喜欢
    • 1970-01-01
    • 2011-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-17
    • 1970-01-01
    • 1970-01-01
    • 2015-11-26
    相关资源
    最近更新 更多