Joh*_*ohn 17 erd database-design
我应该如何在 SQL 中实现以下实体关系图中描述的场景?
如图所示,每个A
实体类型出现都必须与至少一个 B
对应项(由双连接线表示)相关,反之亦然。我知道我应该创建以下三个表:
CREATE TABLE A
(
a INT NOT NULL,
CONSTRAINT A_PK PRIMARY KEY (a)
);
CREATE TABLE B
(
b INT NOT NULL,
CONSTRAINT B_PK PRIMARY KEY (b)
);
CREATE TABLE R
(
a INT NOT NULL,
b INT NOT NULL,
CONSTRAINT R_PK PRIMARY KEY (a, b),
CONSTRAINT R_to_A_FK FOREIGN KEY (a)
REFERENCES A (a),
CONSTRAINT R_to_B_FK FOREIGN KEY (b)
REFERENCES B (b)
);
Run Code Online (Sandbox Code Playgroud)
但是,如何实现总参与约束(即,强制执行其中之一的每个实例A
或至少B
涉及与另一个关系的发生)?
ype*_*eᵀᴹ 16
在 SQL 中做到这一点并不容易,但并非不可能。如果您希望仅通过 DDL 强制执行此操作,则 DBMS 必须实施DEFERRABLE
约束。这可以完成(并且可以检查在 Postgres 中工作,已经实现了它们):
-- lets create first the 2 tables, A and B:
CREATE TABLE a
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT a_pk PRIMARY KEY (aid)
);
CREATE TABLE b
( bid INT NOT NULL,
aid INT NOT NULL,
CONSTRAINT b_pk PRIMARY KEY (bid)
);
-- then table R:
CREATE TABLE r
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT r_pk PRIMARY KEY (aid, bid),
CONSTRAINT a_r_fk FOREIGN KEY (aid) REFERENCES a,
CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
);
Run Code Online (Sandbox Code Playgroud)
到这里是“正常”的设计,其中每一个A
可以与零,一个或多个B
,每个B
可与零,一个或多个A
。
“总参与”限制需要相反顺序的约束(分别来自A
和B
,引用R
)。有FOREIGN KEY
相反的方向约束(从X到Y和从Y到X)正在形成一个圆圈(一个“鸡和蛋”的问题),以及为什么我们需要他们的一个最起码要的DEFERRABLE
。在这种情况下,我们有两个圆(A -> R -> A
和B -> R -> B
,所以我们需要两个延迟约束:
-- then we add the 2 constraints that enforce the "total participation":
ALTER TABLE a
ADD CONSTRAINT r_a_fk FOREIGN KEY (aid, bid) REFERENCES r
DEFERRABLE INITIALLY DEFERRED ;
ALTER TABLE b
ADD CONSTRAINT r_b_fk FOREIGN KEY (aid, bid) REFERENCES r
DEFERRABLE INITIALLY DEFERRED ;
Run Code Online (Sandbox Code Playgroud)
然后我们可以测试我们可以插入数据。请注意,INITIALLY DEFERRED
不需要。我们可以将约束定义为,DEFERRABLE INITIALLY IMMEDIATE
但是我们必须SET CONSTRAINTS
在事务期间使用该语句来推迟它们。但是,在每种情况下,我们都需要在单个事务中插入表中:
-- insert data
BEGIN TRANSACTION ;
INSERT INTO a (aid, bid)
VALUES
(1, 1), (2, 5),
(3, 7), (4, 1) ;
INSERT INTO b (aid, bid)
VALUES
(1, 1), (1, 2),
(2, 3), (2, 4),
(2, 5), (3, 6),
(3, 7) ;
INSERT INTO r (aid, bid)
VALUES
(1, 1), (1, 2),
(2, 3), (2, 4),
(2, 5), (3, 6),
(3, 7), (4, 1),
(4, 2), (4, 7) ;
END ;
Run Code Online (Sandbox Code Playgroud)
在SQLfiddle测试。
如果 DBMS 没有DEFERRABLE
约束,一种解决方法是将A (bid)
和B (aid)
列定义为NULL
。然后,INSERT
过程/语句必须首先插入A
和B
(分别在bid
和 中放入空值aid
),然后插入R
并更新上面的空值到来自 的相关非空值R
。
使用这种方法,DBMS 不会单独通过 DDL 强制执行要求,而是必须考虑并相应地调整每个INSERT
(和UPDATE
和DELETE
和MERGE
)过程,并且必须限制用户仅使用它们并且不能直接对表进行写访问。
FOREIGN KEY
许多最佳实践并不认为在约束中使用圆圈,并且出于充分的理由,复杂性就是其中之一。例如,使用第二种方法(使用可空列),更新和删除行仍然需要使用额外的代码来完成,具体取决于 DBMS。以SQL Server为例,你不能只放,ON DELETE CASCADE
因为当有FK圈时不允许级联更新和删除。
另请阅读此相关问题的答案:
如何与特权儿童建立一对多关系?
另一种第三种方法(请参阅我在上述问题中的回答)是完全删除圆形 FK。因此,保持代码的第一部分(附表A
,B
,R
和外键只有来自R A和B)几乎完好无损(实际上简化了它),我们添加另一个表A
以“必须有一个”相关项目的存储B
。因此,该A (bid)
列移动到A_one (bid)
B 到 A 的反向关系也是如此:
CREATE TABLE a
( aid INT NOT NULL,
CONSTRAINT a_pk PRIMARY KEY (aid)
);
CREATE TABLE b
( bid INT NOT NULL,
CONSTRAINT b_pk PRIMARY KEY (bid)
);
-- then table R:
CREATE TABLE r
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT r_pk PRIMARY KEY (aid, bid),
CONSTRAINT a_r_fk FOREIGN KEY (aid) REFERENCES a,
CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
);
CREATE TABLE a_one
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT a_one_pk PRIMARY KEY (aid),
CONSTRAINT r_a_fk FOREIGN KEY (aid, bid) REFERENCES r
);
CREATE TABLE b_one
( bid INT NOT NULL,
aid INT NOT NULL,
CONSTRAINT b_one_pk PRIMARY KEY (bid),
CONSTRAINT r_b_fk FOREIGN KEY (aid, bid) REFERENCES r
);
Run Code Online (Sandbox Code Playgroud)
第一种和第二种方法的区别在于没有循环 FK,因此级联更新和删除将正常工作。“完全参与”的实施不是 DDL 单独执行的,如第二种方法,必须通过适当的程序 ( INSERT/UPDATE/DELETE/MERGE
)来完成。与第二种方法的一个细微差别是所有列都可以定义为不可为空。
另一种第四种方法(参见@Aaron Bertrand在上述问题中的回答)是使用过滤/部分唯一索引,如果它们在您的 DBMS 中可用(R
对于这种情况,您需要其中两个,在表中)。这与第 3 种方法非常相似,只是您不需要 2 个额外的表。“总参与”约束仍需通过代码应用。
你不能直接。对于初学者来说,如果没有 B 已存在,您将无法插入 A 记录,但如果没有 A 记录,则无法创建 B 记录。有多种方法可以使用触发器之类的东西来强制执行它 - 您必须检查每次插入和删除时,AB 链接表中至少保留一个相应的记录。