ype*_*eᵀᴹ 23 foreign-key database-design referential-integrity
受到 Django 建模问题的启发:Database Modeling with multiple many-to-many Relations in Django。db-design 是这样的:
CREATE TABLE Book
( BookID INT NOT NULL
, BookTitle VARCHAR(200) NOT NULL
, PRIMARY KEY (BookID)
) ;
CREATE TABLE Tag
( TagID INT NOT NULL
, TagName VARCHAR(50) NOT NULL
, PRIMARY KEY (TagID)
) ;
CREATE TABLE BookTag
( BookID INT NOT NULL
, TagID INT NOT NULL
, PRIMARY KEY (BookID, TagID)
, FOREIGN KEY (BookID) REFERENCES Book (BookID)
, FOREIGN KEY (TagID) REFERENCES Tag (TagID)
) ;
CREATE TABLE Aspect
( AspectID INT NOT NULL
, AspectName VARCHAR(50) NOT NULL
, PRIMARY KEY (AspectID)
) ;
CREATE TABLE TagAspect
( TagID INT NOT NULL
, AspectID INT NOT NULL
, PRIMARY KEY (TagID, AspectID)
, FOREIGN KEY (TagID) REFERENCES Tag (TagID)
, FOREIGN KEY (AspectID) REFERENCES Aspect (AspectID)
) ;
Run Code Online (Sandbox Code Playgroud)

问题是如何定义BookAspectRating表并强制执行参照完整性,因此不能(Book, Aspect)为无效的组合添加评级。
AFAIK,复杂 CHECKASSERTIONS涉及子查询和多个表的约束(或)可能解决这个问题,在任何 DBMS 中都不可用。
另一个想法是使用(伪代码)视图:
CREATE VIEW BookAspect_view
AS
SELECT DISTINCT
bt.BookId
, ta.AspectId
FROM
BookTag AS bt
JOIN
Tag AS t ON t.TagID = bt.TagID
JOIN
TagAspect AS ta ON ta.TagID = bt.TagID
WITH PRIMARY KEY (BookId, AspectId) ;
Run Code Online (Sandbox Code Playgroud)
以及具有上述视图的外键的表:
CREATE TABLE BookAspectRating
( BookID INT NOT NULL
, AspectID INT NOT NULL
, PersonID INT NOT NULL
, Rating INT NOT NULL
, PRIMARY KEY (BookID, AspectID, PersonID)
, FOREIGN KEY (PersonID) REFERENCES Person (PersonID)
, FOREIGN KEY (BookID, AspectID)
REFERENCES BookAspect_view (BookID, AspectID)
) ;
Run Code Online (Sandbox Code Playgroud)
三个问题:
是否有 DBMS 允许(可能物化)VIEW带有PRIMARY KEY?
是否有DBMS允许一个FOREIGN KEY是REFERENCES一个VIEW(而不仅仅是基础TABLE)?
是否可以通过其他方式解决此完整性问题 - 使用可用的 DBMS 功能?
澄清:
因为可能没有 100% 令人满意的解决方案 - 而且 Django 问题甚至不是我的问题!- 我对可能攻击问题的一般策略更感兴趣,而不是详细的解决方案。因此,像“在 DBMS-X 中这可以通过表 A 上的触发器来完成”这样的答案是完全可以接受的。
在 Oracle 中,以声明方式强制执行此类约束的一种方法是创建一个物化视图,该视图设置为在提交时快速刷新,其查询标识所有无效行(即BookAspectRating在 中没有匹配项的行BookAspect_view)。然后,您可以在该实体化视图上创建一个简单的约束,如果实体化视图中有任何行,则会违反该约束。这样做的好处是可以最大限度地减少必须在物化视图中复制的数据量。然而,它可能会导致问题,因为约束仅在您提交事务时强制执行——许多应用程序没有被编写为预期提交操作可能会失败——并且因为违反约束可能有点困难与特定行或特定表相关联。
可以仅使用约束在模型中强制执行此业务规则。下表应该可以解决您的问题。使用它代替您的视图:
CREATE TABLE BookAspectCommonTagLink
( BookID INT NOT NULL
, AspectID INT NOT NULL
, TagID INT NOT NULL
--TagID is deliberately left out of PK
, PRIMARY KEY (BookID, AspectID)
, FOREIGN KEY (BookID, TagID)
REFERENCES BookTag (BookID, TagID)
, FOREIGN KEY (AspectID, TagID)
REFERENCES AspectTag (AspectID, TagID)
) ;
Run Code Online (Sandbox Code Playgroud)
我想你会发现在很多情况下,复杂的业务规则不能单独通过模型来执行。这是其中一种情况,至少在 SQL Server 中,我认为触发器(最好是而不是触发器)更好地满足您的目的。