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