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

Mar*_*man 2 sql-server triggers sql-server-2005

我有一个表格,其中包含可以成为账单一部分的记录.我可以知道哪些已经是账单的一部分,因为该表有一个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;
Run Code Online (Sandbox Code Playgroud)

然而,还有一个皱纹.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
Run Code Online (Sandbox Code Playgroud)

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

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

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

Aar*_*and 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
Run Code Online (Sandbox Code Playgroud)

这并不完美.我遗漏的标准有一个原因:确定列值是否已更改可能很复杂,因为它取决于数据类型,列是否可以为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
Run Code Online (Sandbox Code Playgroud)