我有一张表格,其中包含可以成为账单一部分的记录。我可以知道哪些已经是账单的一部分,因为该表有一个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。
答案 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