Пакеты могут иметь несколько счетов, которые могут иметь несколько строк биллинга. У меня есть 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.