UPDATE 触发器在批量更新时运行多次

Trigger for UPDATE runs many time on batch Updates

我的所有表都有用于 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 行...

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

您的代码存在问题,您正在交叉连接 inserteddeleted。在多行更新中,两者都包含许多行,笛卡尔积相乘。

您似乎真的想记录“新”行(插入或更新)。如果是这样,您不想从 deleted select。此外,条件逻辑可以在单个查询中移动,这样可以简化您的代码,如下所示:

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 来自 inserteddeleted.

的两个查询 select

您正在交叉连接 inserteddeleted。通常,它们将使用 table 的主键连接,大概是 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:

在 table 中将整个触发器实现为单个查询
    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;