如何防止更新表,但有一种情况例外

时间:2012-02-29 21:36:22

标签: sql-server sql-server-2005 triggers

我有一张表格,其中包含可以成为账单一部分的记录。我可以知道哪些已经是账单的一部分,因为该表有一个BillId列,当发生这种情况时,它会被应用程序代码更新。我想阻止更新任何具有非null 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

有了这些触发器,向Bill添加Item总会导致RAISERROR触发。大概是因为当填充BillId时,Item_Update_AnyBilled允许它通过,因为deleted.BillId为NULL,但是Item_Update_Modified然后被执行,并且该次要更改导致Item_Update_AnyBilled再次执行,并且这次被删除.BillId不再是NULL。 / p>

除了正在填充BillId或仅对Modified列进行更改的情况外,如何防止对Item表进行更新?

我更喜欢一种解决方案,它不需要我比较每列的插入值和删除值(或使用COLUMNS_UPDATED()),因为这会产生维护问题(有人必须记住更新触发器从表中添加或删除新列的时间。我正在使用SQL Server 2005。

1 个答案:

答案 0 :(得分:5)

为什么不使用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