微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

我应该如何在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个额外的表. “总参与”约束仍然需要通过代码来应用.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


SELECT a.*,b.dp_name,c.pa_name,fm_name=(CASE WHEN a.fm_no='LJCG001H' THEN dbo.ELTPNAME(a.fw_nu) ELSE d.fm_name END),e.fw_state_nm,f.fw_rmk_nm
if not exists(select name from syscolumns where name='tod_no' and id=object_id('iebo09d12')) alter table iebo09d12 add tod_no varchar(
select a.*,pano=a.pa_no,b.pa_name,f.dp_name,e.fw_state_nm,g.fa_name from LJSS007H a (nolock) Left join LJPA002H b (nolock) On a.pa_no =b.pa_no Left jo
要在 SQL Server 2019 中设置定时自动重启,可以使用 Windows 任务计划程序。下面是详细的步骤: 步骤一:创建批处理文件 打开记事本。 输入以下内容: net stop "SQL Server (MSSQLSERVER)" net start "SQ
您收到的错误消息表明数据库 'EastRiver' 的事务日志已满,导致数据库操作失败。要解决这个问题,可以按照以下步骤操作: 1. 备份事务日志首先,备份事务日志以释放空间: BACKUP LOG [EastRiver] TO DISK = N'C:\Backup\East
首先我需要查询出需要使用SQL Server Profiler跟踪的数据库标识ID,若不知道怎么查询数据库的标识ID, 打开SQL Server management studio,点击工具。选择SQL Server Profiler。 登录,登录成功后,如果有个默认弹窗,先取消 新建追踪 命名
--最新的解决方法 --先创建用户帐户,不进行授权,然后通过下面的SQL语句将该用户帐户关联至对应的数据库用户。优点是避免了重新授权的操作。 USE tempdbEXEC sp_change_users_login 'Update_One', 'iemis', &#3
命令: ALTER TABLE 表名 add 列名 数据类型 default 默认值 not null 例如: ALTER TABLE LJEL005H add el_req int default 15 not null
declare @i int set @i=340 while @i<415 begin set @i=@iʱ insert into LJWK007H select '2024','28','9110','3PTSD621000000