Если у меня есть простая таблица пользователей в моей базе данных и простую таблицу Item с user.id в качестве внешнего ключа, таким образом:каскадных ромбовидные удалений в SQL
(id UNIQUEIDENTIFIER DEFAULT (NEWID()) NOT NULL,
name NVARCHAR (MAX) NULL,
email NVARCHAR (128) NULL,
authenticationId NVARCHAR (128) NULL,
createdAt DATETIME DEFAULT GETDATE() NOT NULL,
PRIMARY KEY (id))
CREATE TABLE Items
(id UNIQUEIDENTIFIER DEFAULT (NEWID()) NOT NULL,
userId UNIQUEIDENTIFIER NOT NULL,
name NVARCHAR (MAX) NULL,
description NVARCHAR (MAX) NULL,
isPublic BIT DEFAULT 0 NOT NULL,
createdAt DATETIME DEFAULT GETDATE() NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (userId) REFERENCES Users (id))
Если пользователь удаляется из таблицы Мне нужно, чтобы все связанные элементы сначала удалялись, чтобы избежать нарушения ограничений ссылочной целостности. Это легко сделать с CASCADE DELETE
CREATE TABLE Items
(id UNIQUEIDENTIFIER DEFAULT (NEWID()) NOT NULL,
userId UNIQUEIDENTIFIER NOT NULL,
name NVARCHAR (MAX) NULL,
description NVARCHAR (MAX) NULL,
isPublic BIT DEFAULT 0 NOT NULL,
createdAt DATETIME DEFAULT GETDATE() NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (userId) REFERENCES Users (id) ON DELETE CASCADE)
Но если у меня есть коллекции, ссылки пользователей и таблица собирая предметы в коллекцию я в беде, то есть следующий дополнительный код не работает.
CREATE TABLE Collections
(id UNIQUEIDENTIFIER DEFAULT (NEWID()) NOT NULL,
userId UNIQUEIDENTIFIER NOT NULL,
name NVARCHAR (MAX) NULL,
description NVARCHAR (MAX) NULL,
isPublic BIT DEFAULT 0 NOT NULL,
layoutSettings NVARCHAR (MAX) NULL,
createdAt DATETIME DEFAULT GETDATE() NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (userId) REFERENCES Users (id) ON DELETE CASCADE)
CREATE TABLE CollectedItems
(itemId UNIQUEIDENTIFIER NOT NULL,
collectionId UNIQUEIDENTIFIER NOT NULL,
createdAt DATETIME DEFAULT GETDATE() NOT NULL,
PRIMARY KEY CLUSTERED (itemId, collectionId),
FOREIGN KEY (itemId) REFERENCES Items (id) ON DELETE CASCADE,
FOREIGN KEY (collectionId) REFERENCES Collections (id) ON DELETE CASCADE)
Ошибка указывает, что это «может вызвать циклы или несколько каскадных путей». Способ вокруг этого, я вижу, рекомендовал:
- Редизайн таблиц, но я не вижу, как; или, и часто указывается как "a last resort"
- Использовать триггеры.
Так я удалить ON DELETE CASCADE
и instead use triggers (documentation), как это:
CREATE TRIGGER DELETE_User
ON Users
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON
DELETE FROM Items WHERE userId IN (SELECT id FROM DELETED)
DELETE FROM Collections WHERE userId IN (SELECT id FROM DELETED)
DELETE FROM Users WHERE id IN (SELECT id FROM DELETED)
END
CREATE TRIGGER DELETE_Item
ON Items
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON
DELETE FROM CollectedItems WHERE itemId IN (SELECT id FROM DELETED)
DELETE FROM Items WHERE id IN (SELECT id FROM DELETED)
END
CREATE TRIGGER DELETE_Collection
ON Collections
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON
DELETE FROM CollectedItems WHERE collectionId IN (SELECT id FROM DELETED)
DELETE FROM Collections WHERE id IN (SELECT id FROM DELETED)
END
Однако это не удается, хотя тонко. У меня есть куча модульных тестов (написано в xUnit). Индивидуально тесты всегда проходят. Но запустить в массовом порядке некоторые случайным образом сбой с SQL тупик. В another answer Я указал на SQL Profiler, который показывает тупик между двумя вызовами на удаление.
Каков правильный способ решения этих каскадов удаления алмазов?
Какая СУБД для этого? Добавьте тег, чтобы указать, используете ли вы 'mysql',' postgresql', 'sql-server',' oracle' или 'db2' - или что-то еще. –
Поскольку вы говорите, что тесты не терпят неудачу «массово», я думаю, что тупик не встречается среди триггеров DELETE, которые мне кажутся прекрасными. Поэтому, возможно, вам нужно смотреть дальше, чем просто триггеры. – Oliver
Они включены в триггеры удаления (хотя из профилировщика это сложно получить). Я отредактирую вопрос и поставлю скриншот и еще одну деталь тупика, хотя это довольно сложно, поскольку образец, который я имею заданный в вопросе, упрощается из реальных таблиц. – dumbledad