INSTEAD OF DELETE触发器与ON DELETE CASCADE FK发生冲突

时间:2011-06-23 15:36:24

标签: sql-server sql-server-2005 triggers cascading-deletes

批次可以有多个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触发器有关。

2 个答案:

答案 0 :(得分:1)

我知道这是一个老问题,但是它应该得到答案:

在子表定义了ON DELETE CASCADE触发器时不能指定INSTEAD OF DELETE的原因是,在触发器中,您可能决定不删除子表行,从而妨碍级联生效。

由于无法确定是否有可能级联,因此数据库不知道如何处理这种情况,因此将问题留给开发人员解决。

答案 1 :(得分:0)

在我看来,你不需要INSTEAD OF触发器。 INSTEAD OF触发器工作总是代替操作。 您希望在某些情况下抛出异常 - 并在其他情况下删除。

所以你可以使用DELETE CASCADE和普通(AFTER)触发器。

在触发器内部,您可以RAISERROR异常并可能ROLLBACK您的事务。 (触发器周围总是存在隐式事务)