Триггер для ОБНОВЛЕНИЯ запускается много раз при пакетных обновлениях - PullRequest
1 голос
/ 11 июля 2020

Все мои таблицы имеют триггер для операций 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 строк ...

Что не так с моим триггером и как я могу решить это?

Ответы [ 2 ]

3 голосов
/ 11 июля 2020

Проблема с вашим кодом в том, что вы перекрестно соединяете inserted и deleted. При многострочном обновлении обе содержат много строк, которые умножаются декартовым произведением.

Похоже, вы действительно хотите зарегистрировать «новые» строки (вставленные или обновленные). Если это так, вы не хотите выбирать из deleted. Кроме того, условные logi c могут быть перемещены в рамках одного запроса, что позволяет упростить ваш код следующим образом:

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 голосов
/ 11 июля 2020

Вы объединяете 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;
...