Как предотвратить обновления таблицы, за исключением одной ситуации - PullRequest
2 голосов
/ 01 марта 2012

У меня есть таблица, которая содержит записи, которые могут стать частью счета.Я могу сказать, какие из них уже являются частью счета, потому что в таблице есть столбец BillId, который обновляется кодом приложения, когда это происходит.Я хочу запретить обновления любой записи с ненулевым идентификатором BillId.Я думаю, что следующее должно позаботиться об этом:

CREATE TRIGGER [Item_Update_AnyBilled]
ON [dbo].[Item]
FOR UPDATE
AS 
BEGIN
SET NOCOUNT ON;
DECLARE @AnyBilled BIT;

SELECT  TOP(1) @AnyBilled = 1
  FROM  inserted i
        JOIN deleted d ON i.ItemId = d.ItemId
 WHERE  d.BillId IS NOT NULL; 

IF COALESCE(@AnyBilled, 0) = 1 BEGIN
    RAISERROR(2870486, 16, 1);  -- Cannot update a record that is part of a bill.
    ROLLBACK TRANSACTION;
END;
END;

Однако есть еще одна морщина.В таблице Item также есть столбец DATETIME Modified и триггер, который его обновляет.

CREATE TRIGGER [dbo].Item_Update_Modified 
ON  [dbo].[Item] 
AFTER UPDATE
AS 
BEGIN
SET NOCOUNT ON;

UPDATE a
   SET Modified = getdate()
  FROM Item a JOIN inserted i ON i.ItemId = a.ItemId
END

При наличии этих триггеров добавление элемента в счет всегда вызывает срабатывание RAISERROR.Предположительно потому, что когда заполнен BillId, Item_Update_AnyBilled пропускает его, потому что delete.BillId имеет значение NULL, но затем выполняется Item_Update_Modified, и это вторичное изменение приводит к повторному выполнению Item_Update_AnyBilled, и на этот раз удаленный. BillId больше не равен NULL.1007 *

Как я могу предотвратить обновления таблицы Item, за исключением случая, когда заполняется BillId или когда единственное изменение относится к столбцу Modified?

Я бы предпочел решение, которое не требовало бы, чтобы я сравнивал вставленные и удаленные значения каждого столбца (или использовал COLUMNS_UPDATED ()), поскольку это создало бы проблему обслуживания (кто-то должен был бы забыть обновить)триггер каждый раз, когда новый столбец добавляется или удаляется из таблицы).Я использую SQL Server 2005.

1 Ответ

5 голосов
/ 01 марта 2012

Почему бы не использовать INSTEAD OF триггер? Это требует немного больше работы (а именно повторного оператора UPDATE), но каждый раз, когда вы можете запретить работу, вместо того, чтобы позволить ей произойти и затем откатить ее, вам будет лучше.

CREATE TRIGGER [dbo].[Item_BeforeUpdate_AnyBilled]
ON [dbo].[Item]
INSTEAD OF UPDATE
AS 
BEGIN
  SET NOCOUNT ON;

  IF EXISTS 
  (
     SELECT 1 FROM inserted i
       JOIN deleted AS d ON i.ItemId = d.ItemId
       WHERE d.BillId IS NULL -- it was NULL before, may not be NULL now
  )
  BEGIN
     UPDATE src 
       SET col1 = i.col1 --, ... other columns
          ModifiedDate = CURRENT_TIMESTAMP -- this eliminates need for other trigger
       FROM dbo.Item AS src
       INNER JOIN inserted AS i
       ON i.ItemId = src.ItemId
       AND (criteria to determine if at least one column has changed);
  END
  ELSE
  BEGIN
     RAISERROR(...);
  END
END
GO

Это не подходит идеально. Критерии, которые я пропустил, не указаны по причине: может быть сложно определить, изменилось ли значение столбца, поскольку это зависит от типа данных, может ли столбец быть NULL и т. Д. AFAIK встроенные триггерные функции может только сказать, был ли указан определенный столбец, но не изменилось ли значение на самом деле.

РЕДАКТИРОВАТЬ учитывая, что вас интересуют только другие столбцы, которые обновляются из-за триггера после, я думаю, что следующий триггер INSTEAD OF может заменить оба ваших существующих триггера, а также иметь дело с несколькими строки обновляются сразу (некоторые не соответствуют вашим критериям):

CREATE TRIGGER [dbo].[Item_BeforeUpdate_AnyBilled]
ON [dbo].[Item]
INSTEAD OF UPDATE
AS 
BEGIN
  SET NOCOUNT ON;

  UPDATE src SET col1 = i.col1 --, ... other columns,
     ModifiedDate = CURRENT_TIMESTAMP
     FROM dbo.Item AS src
     INNER JOIN inserted AS i
     ON src.ItemID = i.ItemID
     INNER JOIN deleted AS d
     ON i.ItemID = d.ItemID 
     WHERE d.BillID IS NULL; 

  IF @@ROWCOUNT = 0
  BEGIN
    RAISERROR(...);
  END
END
GO
...