加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程 > 正文

数据库设计 – 在SQL中实现与多个参与约束的多对多关系

发布时间:2021-01-14 15:46:25 所属栏目:编程 来源:网络整理
导读:我应该如何在SQL中实现以下实体关系图中描述的场景? 如图所示,每个A实体类型的出现必须与至少一个B对应物相关(由双连接线表示),反之亦然.我知道我应该创建以下三个表: CREATE TABLE A ( a INT NOT NULL,CONSTRAINT A_PK PRIMARY KEY (a) ); CREATE TABLE B

我应该如何在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)
    );

但是,总参与约束的实施情况如何(即,强制执行A??或B的每个实例与另一个实例之间至少涉及一个关系)?

解决方法

在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,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
 );

这里是“正常”设计,其中每个A可以与零,一个或多个B相关,并且每个B可以与零,一个或多个A相关.

“总参与”限制需要以相反的顺序进行约束(分别来自A和B,引用R).在相反方向(从X到Y以及从Y到X)具有FOREIGN KEY约束正在形成一个圆(“鸡和蛋”问题),这就是为什么我们需要其中一个至少是可延伸的.在这种情况下,我们有两个圆圈(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 ;

然后我们可以测试我们可以插入数据.请注意,不需要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,(1,2),3),4),6),7) ;

    INSERT INTO r (aid,7) ; 
 END ;

测试时间为SQLfiddle.

如果DBMS没有DEFERRABLE约束,则一种解决方法是将A(bid)和B(辅助)列定义为NULL.然后INSERT过程/语句必须首先插入到A和B中(分别在出价和辅助中放置空值),然后插入到R中,然后将上面的空值更新为来自R的相关非空值.

使用这种方法,DBMS不会仅通过DDL强制执行这些要求,但必须相应地考虑和调整每个INSERT(以及UPDATE和DELETE和MERGE)过程,并且必须限制用户仅使用它们,并且不能直接写入表格.

许多最佳实践并未考虑在FOREIGN KEY约束中使用圆圈,并且出于充分的理由,复杂性是其中之一.例如,使用第二种方法(具有可为空的列),仍然必须使用额外的代码来更新和删除行,具体取决于DBMS.例如,在SQL Server中,您不能只使用ON DELETE CASCADE,因为当存在FK圈时,不允许级联更新和删除.

请阅读此相关问题的答案:
How to have a one-to-many relationship with a privileged child?

另一种第三种方法(参见我在上述问题中的答案)是完全去除圆形FK.因此,保持代码的第一部分(表A,B,R和外键仅从R到A和B)几乎完整(实际上简化它),我们为A添加另一个表来存储“必须有一个”来自B的相关项目.因此,A(出价)栏移至A_one(出价)对于从B到A的反向关系也是如此:

CREATE TABLE a 
( aid INT NOT NULL,CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
 );

CREATE TABLE a_one 
( aid 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,CONSTRAINT b_one_pk PRIMARY KEY (bid),CONSTRAINT r_b_fk FOREIGN KEY (aid,bid) REFERENCES r
 );

第一种方法和第二种方法的区别在于没有循环FK,因此级联更新和删除工作正常. “全员参与”的执行不仅仅是DDL,如第二种方法,必须通过适当的程序(INSERT / UPDATE / DELETE / MERGE)来完成.与第二种方法的一个细微差别是所有列都可以定义为不可为空.

另一个第4种方法(参见上述问题中的@Aaron Bertrand’s answer)是使用过滤/部分唯一索引(如果它们在您的DBMS中可用)(对于这种情况,您需要其中两个,在R表中).这与第3种方法非常相似,只是您不需要2个额外的表. “总参与”约束仍然需要通过代码来应用.

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读