2013-11-28 6 views
1

Я пытаюсь развернуть свою голову вокруг триггера ПОСЛЕ ОБНОВЛЕНИЯ.After Update Trigger puzzlement

В настоящее время в нашей БД есть триггер, содержащий курсор. По моему мнению, курсоры в триггерах обычно плохо работают, поэтому я пытаюсь избавиться от курсора.

В настоящее время запуска выглядит следующим образом:

ALTER TRIGGER [dbo].[trg_TaskMovement_Zone] ON [dbo].[Tasks_Movement] 
AFTER INSERT, UPDATE 
AS 
BEGIN 
    SET NOCOUNT ON 

    DECLARE @rowcheck int 
    DECLARE @MovementID INT 
    DECLARE @SiteFromID INT 
    DECLARE @SiteToID INT 
    DECLARE @SiteResponsibleID INT 
    DECLARE @FromAddress_Postcode Varchar(20) 
    DECLARE @ToAddress_Postcode Varchar(20) 

    DECLARE zcursor CURSOR FOR SELECT ID, SiteFromID, SiteToID, SiteResponsibleID 
    , FromAddress_Postcode, ToAddress_Postcode FROM inserted 

    OPEN zcursor  

    SELECT @rowcheck=1 
    WHILE @rowcheck=1  
    BEGIN 
     FETCH NEXT FROM zcursor INTO @MovementID, @SiteFromID, @SiteToID, @SiteResponsibleID, @FromAddress_Postcode, @ToAddress_Postcode 
     IF (@@FETCH_STATUS = 0) 

     BEGIN 
      UPDATE Tasks_Movement 
      SET  ZoneFromID = dbo.fn_GetZoneFromPostcode(@FromAddress_Postcode), 
        ZoneToID = dbo.fn_GetZoneFromPostcode(@ToAddress_Postcode) 
      WHERE Tasks_Movement.ID = @MovementID 

      UPDATE Tasks_Movement 
      SET  SiteResponsibleID = [dbo].[fn_GetDefaultDepotResponsibleForSite](@SiteFromID) 
      WHERE Tasks_Movement.ID = @MovementID 
       AND (@SiteResponsibleID Is NULL OR @SiteResponsibleID=0) 
       AND (@SiteFromID Is NOT NULL AND @SiteFromID>0) 

      UPDATE Tasks_Movement 
      SET  SiteResponsibleID = [dbo].[fn_GetDefaultDepotResponsibleForSite](@SiteToID) 
      WHERE Tasks_Movement.ID = @MovementID 
       AND (@SiteResponsibleID Is NULL OR @SiteResponsibleID=0) 
       AND (@SiteToID Is NOT NULL AND @SiteToID>0) 


     END 
     ELSE 
      SELECT @rowcheck=0 
    END 

    CLOSE zcursor 
    DEALLOCATE zcursor 
END 

Из того, что я могу сказать, курсор в это совершенно не нужно

Я был бы прав, считая, что следующий будет работать лучше (?):

ALTER TRIGGER [dbo].[trg_TaskMovement_Zone] ON [dbo].[Tasks_Movement] 
    AFTER INSERT, UPDATE 
AS 
BEGIN 
    SET NOCOUNT ON 

      UPDATE Tasks_Movement 
      SET  ZoneFromID = dbo.fn_GetZoneFromPostcode(inserted.FromAddress_Postcode), 
        ZoneToID = dbo.fn_GetZoneFromPostcode(inserted.ToAddress_Postcode) 
      FROM inserted 
      WHERE Tasks_Movement.ID IN (SELECT id FROM inserted) 

      UPDATE Tasks_Movement 
      SET  SiteResponsibleID = [dbo].[fn_GetDefaultDepotResponsibleForSite](inserted.SiteFromID) 
      FROM inserted 
      WHERE Tasks_Movement.ID IN (SELECT id FROM inserted 
              WHERE (inserted.SiteResponsibleID Is NULL OR inserted.SiteResponsibleID=0) 
              AND (inserted.SiteFromID Is NOT NULL AND inserted.SiteFromID>0)) 

      UPDATE Tasks_Movement 
      SET  SiteResponsibleID = [dbo].[fn_GetDefaultDepotResponsibleForSite](@SiteToID) 
      FROM inserted 
      WHERE Tasks_Movement.ID IN (SELECT id FROM inserted 
              WHERE (inserted.SiteResponsibleID Is NULL OR inserted.SiteResponsibleID=0) 
              AND (inserted.SiteToID Is NOT NULL AND inserted.SiteToID>0)) 

END 
+0

Пожалуйста, сообщите нам, что ** конкретная система баз данных ** это для - многие вещи зависят от поставщика. Вы используете MySQL? Postgres? SQL Server? Oracle? IBM DB2? Что-то еще? Обновите свои теги, чтобы показать, какую систему баз данных (и какую версию!) Вы используете - спасибо! –

+1

Извините marc ... Это для SQL Server. Я обновил теги сейчас. – MrDaveM

+0

Подход курсора медленный, когда вы переходите через большой набор строк. В вашем случае у вас есть только вставленная строка, поэтому удаление курсора не является проблемой. – NoChance

ответ

1

Я думаю, что триггер должен быть чем-то вроде этого:

ALTER TRIGGER [dbo].[trg_TaskMovement_Zone] ON [dbo].[Tasks_Movement] 
    AFTER INSERT, UPDATE 
AS 
BEGIN 
    SET NOCOUNT ON 

    UPDATE tm 
    SET  ZoneFromID = dbo.fn_GetZoneFromPostcode(i.FromAddress_Postcode), 
      ZoneToID = dbo.fn_GetZoneFromPostcode(i.ToAddress_Postcode) 
    FROM Tasks_Movement tm 
      INNER JOIN inserted i 
       ON i.ID = tm.ID; 

    UPDATE tm 
    SET  SiteResponsibleID = [dbo].[fn_GetDefaultDepotResponsibleForSite](i.SiteFromID) 
    FROM Tasks_Movement tm 
      INNER JOIN inserted i 
       ON i.ID = tm.ID 
    WHERE (i.SiteResponsibleID IS NULL OR i.SiteResponsibleID = 0) 
    AND  i.SiteFromID > 0 

    UPDATE tm 
    SET  SiteResponsibleID = [dbo].[fn_GetDefaultDepotResponsibleForSite](i.SiteToID) 
    FROM Tasks_Movement tm 
      INNER JOIN inserted i 
       ON i.ID = tm.ID 
    WHERE (i.SiteResponsibleID IS NULL OR i.SiteResponsibleID = 0) 
    AND  i.SiteToID > 0 
END 

Я изменил его, чтобы использовать синтаксис SQl Server UPDATE .. FROM, а также удалил избыточную нулевую проверку, когда вы проверяете, является ли идентификатор сайта> 0. NULL не больше или меньше 0, поэтому, если SiteID имеет значение null SiteID > 0 никогда не может оценивать значение true, поэтому это лишняя дополнительная проверка.

Наконец, я бы также рекомендовал удалить определенные пользователем функции, хотя я не могу видеть их под капотом, поскольку они выглядят очень похожими на простые функции loukup, которые могут быть достигнуты гораздо эффективнее с присоединяется.


EDIT

Вместо того чтобы использовать функцию UPDATE(column) Я хотел бы добавить дополнительный присоединиться к обновлению для фильтрации обновленных строк, например:

UPDATE tm 
SET  SiteResponsibleID = [dbo].[fn_GetDefaultDepotResponsibleForSite](i.SiteToID) 
FROM Tasks_Movement tm 
     INNER JOIN inserted i 
      ON i.ID = tm.ID 
     LEFT JOIN deleted d 
      ON d.ID = i.ID 
WHERE (i.SiteResponsibleID IS NULL OR i.SiteResponsibleID = 0) 
AND  i.SiteToID > 0 
AND  AND ISNULL(i.SiteToID, 0) != ISNULL(d.SiteToID); 

Я бы это сделать это потому что UPDATE(siteToID) вернет true, если любая строка имеет обновленное значение, поэтому, если вы обновите 1000 000 строк, а одно имеет изменения, оно будет выполнять обновление на всех из них, а не только на те, которые изменились, присоединившись к deleted, вы можете ограничить обновление соответствующими строками.

+0

О, хорошо, теперь это немного больше. Спасибо за совет Гарет. – MrDaveM

+0

И могу ли я обернуть эти инструкции UPDATE с помощью IF (UPDATE (FromAddress_Postcode) ИЛИ UPDATE (ToAddress_Postcode)) BEGIN ... END, чтобы остановить его ненужное обновление? – MrDaveM

+0

Я добавил редактирование с альтернативой использованию 'UPDATE()' – GarethD