INSTEAD OF DELETE Конфликт триггера с ON DELETE CASCADE FK - PullRequest
3 голосов
/ 23 июня 2011

Пакеты могут иметь несколько счетов, которые могут иметь несколько строк биллинга. У меня есть ON DELETE CASCADE FK между ними, так что если вы удаляете пакет, связанные с ним записи Bill и BillLine также удаляются. Если вы удалите Билл, связанные БиллЛайны будут удалены, но запись Пакета не будет затронута. Теперь мне нужно предотвратить удаление Билла, если существует определенное условие данных с одной или несколькими связанными записями БилЛайн.

Таблица Билл явно нуждается в триггере INSTEAD OF DELETE. BillLine.BillId имеет ON DELETE CASCADE FK, ссылающуюся на Bill.BillId. Имеет смысл, чтобы вместо этого мне нужно было включить FK ON DELETE NO ACTION, потому что триггер INSTEAD OF DELETE эффективно заменяет функциональность CASCADE. Когда я удаляю счет, INSTEAD OF DELETE либо удаляет связанные записи BillLine, либо выдает исключение в зависимости от определенных условий данных. Пока все хорошо.

Однако, поскольку Bill.BatchId имеет ON DELETE CASCADE FK, ссылающийся на Batch.BatchId, SQL Server не позволит мне создать триггер. Это я не понимаю. Почему я должен создать триггер INSTEAD OF DELETE для Batch только потому, что у меня он есть в 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

Если вы попытаетесь заменить триггер Batch_Delete на ON DELETE CASCADE: DROP TRIGGER [Batch_Delete] ALTER TABLE [Bill] DROP CONSTRAINT [FK_Bill_Batch_BatchId]; ALTER TABLE [Билл] с проверкой и добавлением ограничения [FK_Bill_Batch_BatchId] FOREIGN KEY ([BatchId]) ССЫЛКИ [Batch] ([BatchId]) НА УДАЛЕННОМ КАСКАДЕ;

Вы получите это:

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 в этом направлении должен иметь какое-либо отношение к триггеру INSTEAD OF DELETE в таблице Bill.

1 Ответ

0 голосов
/ 08 июля 2011

По-моему, вам не нужен триггер INSTEAD OF.Триггер INSTEAD OF работает всегда вместо операции.Вы хотите вызвать исключение в некоторых случаях - и удалить в других.

Таким образом, вы можете использовать DELETE CASCADE и обычный (AFTER) триггер.

Внутри триггера вы можете RAISERROR исключение ивероятно откат вашей транзакции.(Всегда есть неявная транзакция вокруг триггера)

...