sql - 如何在简单的三向关系上实现基于约束的参照完整性
问题描述
我有三个简单的关系。TableB
既是TableC
参考TableA
,TableC
也是参考TableB
。
我发现不可能在 SQL Server 中以通过约束强制引用完整性的方式对此进行建模,但它也允许从任何实体中删除记录,而无需复杂且低效的基于触发器的引用完整性检查,或手动删除相关实体正确的顺序。
这是我的架构。
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 Server 不允许它。试图打破这个循环,我尝试了以下方法。
使用set null
约束和after
触发器删除TableC
. 不起作用,SQL Server 拒绝这样做。
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 的已删除用户应该只返回 null。我正在尝试使用 Documents_LastEditedBy 作为映射表来实现这一点。
解决方案
您可以在将 EditedBy UserId 更新为 NULL 的 Users 表上创建一个而不是 delete 触发器:
create table Users (
Id int identity not null,
constraint P_Users_Id primary key (Id),
Name nvarchar(20),
)
go
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 dbo.Users(Id) on delete no action
)
go
insert into dbo.Users(Name) values ('UserA'), ('UserB');
insert into dbo.Documents(CreatedBy) values (1), (2); --doc1 created by userA, doc2 created by userB, doc3 created by
insert into dbo.Documents_LastEditedBy values(1, 2) --document 1 edited by B (?? )
insert into dbo.Documents_LastEditedBy values(2, 1) --document 2 edited by userA
insert into dbo.Documents_LastEditedBy values(2, 2) --document 2 edited by userB
go
select *
from dbo.Users
select *
from dbo.Documents
select *
from Documents_LastEditedBy
go
delete from dbo.Users
where name = 'UserA' --fk violation
go
create trigger dbo.insteadofdeleteusers on dbo.users
instead of delete
as
begin
if not exists(select * from deleted)
begin
return;
end
update dbo.Documents_LastEditedBy
set UserId = null
where UserId in (select id from deleted);
delete
from dbo.Users
where id in (select id from deleted);
end
go
delete from dbo.Users
where name = 'UserA'
go
select *
from dbo.Users --userA gone
select *
from dbo.Documents--document created by userA gone
select *
from Documents_LastEditedBy --last edited userA set to NULL
go
推荐阅读
- android - Kotlin:有没有办法从按钮中获取背景颜色?
- winapi - 尽管 FlushConsoleInputBuffer MASM,输入缓冲区仍保留旧输入
- r - 使用 purrr 逐行减去向量
- python - 自定义可选参数以包含它们自己的位置参数
- workbox - 我无法弄清楚为什么当一条路由的网络出现故障时 Workbox 不从缓存中检索,但适用于所有其他路由?
- python-3.x - Popen什么都不返回
- r - 从邻接矩阵重建二元数据帧的最有效过程是什么?
- sql-server - 出现“函数过程需要未提供的参数”错误,但提供了所有参数
- bash - git apply 错误没有这样的文件或目录
- javascript - 在打开图层中单击图层时的回调