У меня три простых отношения. TableB
и TableC
оба ссылаются на TableA
, а TableC
также ссылаются на TableB
.
Я считаю невозможным смоделировать это на SQL сервере таким образом, чтобы обеспечить ссылочную целостность через ограничения, но также позволяет удалять записи из любого объекта, не требуя сложной или неэффективной проверки ссылочной целостности на основе триггера, или удаления связанных объектов вручную в правильном порядке.
Вот моя схема.
create table TableA (
Id int identity not null,
constraint P_TableA_Id primary key (Id)
)
create table TableB (
Id int identity not null,
constraint P_TableB_Id primary key (Id),
ARef int,
constraint F_TableB_ARef foreign key (ARef) references TableA(Id) on delete cascade
)
create table TableC (
Id int identity not null,
constraint P_TableC_Id primary key (Id),
ARef int,
constraint F_TableC_ARef foreign key (ARef) references TableA(Id) on delete cascade,
BRef int,
-- Does not work.
--constraint F_TableC_BRef foreign key (BRef) references TableB(Id) on delete cascade
-- Works.
constraint F_TableC_BRef foreign key (BRef) references TableB(Id)
)
Последнее on delete cascade
- это то, что разрушает его, так как SQL Сервер не разрешит это. Пытаясь разорвать этот цикл, я попробовал следующее:
Использование ограничения set null
и триггера after
для удаления строк в TableC
. Не работает, SQL Сервер отказывается это разрешить.
constraint F_TableC_BRef foreign key (BRef) references TableB(Id) on delete set null
Использование триггера Instead of
для удаления TableC
записей при удалении TableB
записей не работает, потому что вы не можете использовать триггер instead of
для любой таблицы с ограничением каскадного удаления.
create trigger T_TableB_delete on TableB instead of delete as
begin
delete from TableC where BRef in (select Id from deleted)
delete from TableB where Id in (select Id from deleted)
end
Триггер after
не будет работать, поскольку попытка удаления из TableB
не удастся из-за включения внешнего ключа TableC.BRef
до запуска триггера.
Одним из решений является кодирование всей проверки ссылочной целостности с использованием триггеров, которая работает, но ужасно сложна и неэффективна.
Другое решение состоит в том, чтобы требовать от клиентов вручную удалите TableC
записей перед TableB
записями.
Возможно, лучшее решение, которое у меня есть на данный момент, - это создание хранимой процедуры для удаления из TableB
, и в этой процедуре сначала сначала удаляются TableC
записи. Но в настоящее время мы не используем хранимые процедуры, поэтому начинать использовать их для решения того, что на первый взгляд кажется очень простой проблемой проектирования, не идеально.
Существуют ли другие решения для этого, которые я пропустили?
ОБНОВЛЕНИЕ
Вот более реалистичная версия того, чего я пытаюсь достичь.
create table Users (
Id int identity not null,
constraint P_Users_Id primary key (Id),
Name nvarchar(20)
)
create table Documents (
Id int identity not null,
constraint P_Documents_Id primary key (Id),
CreatedBy int,
constraint F_Documents_CreatedBy foreign key (CreatedBy) references Users(Id) on delete cascade,
)
create table Documents_LastEditedBy (
DocumentId int,
constraint F_Documents_LastEditedBy_DocumentId foreign key (DocumentId) references Documents(Id) on delete cascade,
UserId int,
constraint F_Documents_UserId foreign key (UserId) references Users(Id) on delete cascade,
)
В этой схеме удаление пользователя следует удалить любые документы, где пользователь является CreateBy. Но удаленные пользователи, которые отображаются в LastEditedBy для документа, должны просто возвращать ноль. Я пытаюсь добиться этого, используя Documents_LastEditedBy в качестве таблицы сопоставления.