批次可以有多个Bills,可以有多个BillLines。我在它们之间有ON DELETE CASCADE FK,这样如果删除批处理,关联的Bill和BillLine记录也会被删除。如果删除帐单,关联的BillLines将被删除,但批记录不受影响。现在,如果某个数据条件包含一个或多个相关的BillLine记录,我需要阻止删除帐单。
表格明显需要一个INSTEAD OF DELETE触发器。 BillLine.BillId有一个引用Bill.BillId的ON DELETE CASCADE FK。我需要将FK ON DELETE NO ACTION改为有意义,因为INSTEAD OF DELETE触发器有效地取代了CASCADE功能。当我删除一个Bill时,INSTEAD OF DELETE将删除相关的BillLine记录或根据某些数据条件引发异常。到目前为止,非常好。
但是,因为Bill.BatchId有一个ON DELETE CASCADE FK引用Batch.BatchId,SQL Server不会让我创建触发器。这个我不明白。为什么我必须在Batch上构建一个INSTEAD OF DELETE触发器,因为我在Bill上有一个?
下面创建表和键的代码(省略了所有无关的列和键)是现在的情况,没有ON DELETE CASCADE子句。问题是,为什么FK_Bill_Batch_BatchId不能有那个子句而不是我必须创建一个额外的INSTEAD OF DELETE触发器?
CREATE TABLE [Batch](
[BatchId] [bigint] NOT NULL,
CONSTRAINT [PK_Batch_BatchId] PRIMARY KEY CLUSTERED
(
[BatchId] ASC
)
)
CREATE TABLE [Bill](
[BillId] [bigint] NOT NULL,
[BatchId] [bigint] NOT NULL,
[ReversesBillId] [bigint] NULL,
CONSTRAINT [PK_Bill_BillId] PRIMARY KEY CLUSTERED
(
[BillId] ASC
)
)
ALTER TABLE [Bill] WITH CHECK ADD CONSTRAINT [FK_Bill_Batch_BatchId] FOREIGN KEY([BatchId])
REFERENCES [Batch] ([BatchId])
ALTER TABLE [Bill] WITH NOCHECK ADD CONSTRAINT [FK_Bill_ReversesBillId] FOREIGN KEY([ReversesBillId])
REFERENCES [Bill] ([BillId])
CREATE TABLE [BillLine](
[BillLineId] [bigint] NOT NULL,
[BillId] [bigint] NOT NULL,
[ReversedByBillLineId] [bigint] NULL,
CONSTRAINT [PK_BillLine_BillLineId] PRIMARY KEY CLUSTERED
(
[BillLineId] ASC
)
)
ALTER TABLE [BillLine] WITH CHECK ADD CONSTRAINT [FK_BillLine_Bill_BillId] FOREIGN KEY([BillId])
REFERENCES [Bill] ([BillId])
ALTER TABLE [BillLine] WITH CHECK ADD CONSTRAINT [FK_BillLine_ReversedByBillLineId] FOREIGN KEY([ReversedByBillLineId])
REFERENCES [BillLine] ([BillLineId])
GO
CREATE TRIGGER [Bill_Delete]
ON [Bill]
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON
DECLARE @BillId UNIQUEIDENTIFIER
DECLARE myCursor CURSOR LOCAL FORWARD_ONLY
FOR SELECT b.[BillId]
FROM deleted b
JOIN [Batch] bt on b.[BatchId] = bt.[BatchId]
OPEN myCursor
FETCH NEXT FROM myCursor INTO @BillId
WHILE @@FETCH_STATUS = 0
BEGIN
-- Delete BillLine records reversed by another BillLine in the same Bill
DELETE FROM [BillLine]
WHERE [BillId] = @BillId
AND [ReversedByBillLineId] IN
(SELECT bl.[BillLineId]
FROM [BillLine] bl
WHERE bl.BillId = @BillId
);
-- Delete all remaining BillLine records for the Bill
-- If the BillLine is reversed by a BillLine in a different Bill, the FK will raise an exception.
-- That is the desired behavior.
DELETE FROM [BillLine]
WHERE [BillId] = @BillId;
-- Delete the Bill
DELETE FROM [Bill]
WHERE [BillId] = @BillId;
FETCH NEXT FROM myCursor INTO @BillId
END
END
GO
CREATE TRIGGER [Batch_Delete]
ON [Batch]
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON
DECLARE @BatchId UNIQUEIDENTIFIER
DECLARE myCursor CURSOR LOCAL FORWARD_ONLY
FOR SELECT [BatchId]
FROM deleted
OPEN myCursor
FETCH NEXT FROM myCursor INTO @BatchId
WHILE @@FETCH_STATUS = 0
BEGIN
-- Delete all Bill records for the Batch.
-- Another INSTEAD OF DELETE trigger on Bill will attempt to delete the associated BillLine records in the correct order.
-- If the BillLine is reversed by a BillLine in a different Bill, FK_BillLine_ReversedByBillLineId will raise an exception.
-- That is the desired behavior.
DELETE FROM [Bill]
WHERE [BatchId] = @BatchId;
FETCH NEXT FROM myCursor INTO @BatchId
END
END
如果您尝试使用ON DELETE CASCADE替换Batch_Delete触发器: DROP TRIGGER [Batch_Delete] ALTER TABLE [Bill] DROP CONSTRAINT [FK_Bill_Batch_BatchId]; ALTER TABLE [Bill] WITH CHECK ADD CONSTRAINT [FK_Bill_Batch_BatchId] FOREIGN KEY([BatchId]) 参考[批处理]([BatchId])ON DELETE CASCADE;
你会得到这个:
Msg 1787, Level 16, State 0, Line 2
Cannot define foreign key constraint 'FK_Bill_Batch_BatchId' with cascaded DELETE or UPDATE on table 'Bill' because the table has an INSTEAD OF DELETE or UPDATE TRIGGER defined on it.
Msg 1750, Level 16, State 0, Line 2
Could not create constraint. See previous errors.
我不明白为什么这个方向的ON DELETE CASCADE应该与Bill表上的INSTEAD OF DELETE触发器有关。
答案 0 :(得分:1)
我知道这是一个老问题,但是它应该得到答案:
在子表定义了ON DELETE CASCADE
触发器时不能指定INSTEAD OF DELETE
的原因是,在触发器中,您可能决定不删除子表行,从而妨碍级联生效。
由于无法确定是否有可能级联,因此数据库不知道如何处理这种情况,因此将问题留给开发人员解决。
答案 1 :(得分:0)
在我看来,你不需要INSTEAD OF触发器。 INSTEAD OF触发器工作总是代替操作。 您希望在某些情况下抛出异常 - 并在其他情况下删除。
所以你可以使用DELETE CASCADE和普通(AFTER)触发器。
在触发器内部,您可以RAISERROR异常并可能ROLLBACK您的事务。 (触发器周围总是存在隐式事务)