如何防止更新表,一种情况除外
我有一个表格,其中包含可以成为账单一部分的记录.我可以分辨哪些已经是账单的一部分,因为该表有一个 BillId 列,当发生这种情况时,应用程序代码会更新该列.我想阻止更新任何具有非空 BillId 的记录.我认为以下应该解决这个问题:
I have a table that contains records that can become part of a bill. I can tell which ones are already part of a bill because the table has a BillId column that gets updated by the application code when that happens. I want to prevent updates to any record that has a non-null BillId. I'm thinking that the following should take care of that:
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 列,以及一个更新它的触发器.
However, there is one more wrinkle. The Item table also has a DATETIME Modified column, and a trigger that updates it.
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 允许它通过,因为已删除的.BillId 为 NULL,但是 Item_Update_Modified 然后被执行,并且二次更改导致 Item_Update_AnyBilled 再次执行,而这次被删除.BillId 不再是 NULL.
With these triggers in place, adding an Item to a Bill always causes the RAISERROR to fire. Presumably because when the BillId is populated, Item_Update_AnyBilled lets it through because the deleted.BillId is NULL, but the Item_Update_Modified then gets executed, and that secondary change causes Item_Update_AnyBilled to get executed again, and this time deleted.BillId is no longer NULL.
如何防止更新 Item 表,除非正在填充 BillId 或唯一的更改是对 Modified 列进行更改?
How can I prevent updates to the Item table except in the case where the BillId is being populated or when the only change is to the Modified column?
我更喜欢不需要我比较每列的插入和删除值(或使用 COLUMNS_UPDATED())的解决方案,因为这会产生维护问题(有人必须记住更新触发器)表中添加或删除新列的时间).我使用的是 SQL Server 2005.
I'd prefer a solution that didn't require me to compare the inserted and deleted values of every column (or use COLUMNS_UPDATED()) as that would create a maintenance issue (someone would have to remember to update the trigger any time a new column is added to or deleted from the table). I am using SQL Server 2005.
推荐答案
为什么不使用 INSTEAD OF
触发器?它需要更多的工作(即重复的 UPDATE
语句),但任何时候你可以阻止工作,而不是让它发生然后回滚它,你会变得更好.>
Why not use an INSTEAD OF
trigger? It requires a bit more work (namely a repeated UPDATE
statement) but any time you can prevent work, instead of letting it happen and then rolling it back, you're going to be better off.
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 内置触发器函数只能判断是否指定了某个列,而不能判断该值是否实际上比之前发生了变化.
This doesn't fit perfectly. The criteria I've left out is left out for a reason: it can be complex to determine if a column value has changed, as it depends on the datatype, whether the column can be NULL, etc. AFAIK the built-in trigger functions can only tell if a certain column was specified, not whether the value actually changed from before.
EDIT 考虑到您只关心由于 after 触发器而更新的其他列,我认为以下 INSTEAD OF
触发器可以替换您的两个现有触发器并处理一次更新的多行(有些不符合您的标准):
EDIT considering that you're only concerned about the other columns that are updated due to the after trigger, I think the following INSTEAD OF
trigger can replace both of your existing triggers and also deal with multiple rows updated at once (some without meeting your criteria):
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
相关文章