【问题标题】:Constraint for one-to-many relationship一对多关系的约束
【发布时间】:2009-04-15 14:41:16
【问题描述】:

我们有两个具有一对多关系的表。我们希望强制执行一个约束,即给定父记录至少存在一个子记录。

这可能吗?

如果不是,您是否会将架构更改得更复杂一些以支持这样的约束?如果有,你会怎么做?

编辑:我使用的是 SQL Server 2005

【问题讨论】:

  • 你没有说你使用的是什么DBMS。

标签: sql sql-server database-design constraints


【解决方案1】:

从模式的角度来看,这样的约束是不可能的,因为您会遇到“鸡或蛋”类型的场景。在这种情况下,当我插入父表时,我必须在子表中有一行,但在父表中有一行之前,我不能在子表中有一行。

这是更好的客户端强制执行。

【讨论】:

    【解决方案2】:

    如果您的后端支持可延迟的约束,就像 PostgreSQL 一样。

    【讨论】:

      【解决方案3】:

      一个简单的不可为空的列怎么样?

      Create Table ParentTable
      (
      ParentID
      ChildID not null,
      Primary Key (ParentID), 
      Foreign Key (ChildID ) references Childtable (ChildID));
      )
      

      如果您的业务逻辑允许并且您有可以从数据库中查询每个新父记录的默认值,那么您可以在父表上使用before insert trigger 来填充不可为空的子列。

      CREATE or REPLACE TRIGGER trigger_name
      BEFORE INSERT
          ON ParentTable
          FOR EACH ROW 
      BEGIN
      
          -- ( insert new row into ChildTable )
          -- update childID column in ParentTable 
      
      END;
      

      【讨论】:

        【解决方案4】:

        这并不是真正的“在客户端更好地执行”,因为在某些数据库实现中执行它是不切实际的。实际上,该作业确实属于数据库,并且至少以下一种解决方法应该有效。

        最终你想要的是将父母限制在孩子身上。这保证了一个孩子的存在。不幸的是,这会导致鸡蛋问题,因为孩子必须指向同一个父母,从而导致约束冲突。

        在系统其余部分没有明显副作用的情况下解决问题需要两种能力之一 - SQL Server 中没有这两种能力。

        1) 延迟约束验证 - 这会导致在事务结束时验证约束。通常它们发生在语句的末尾。这是鸡蛋问题的根源,因为它会阻止您插入第一个子行或父行,因为缺少另一个,这解决了它。

        2) 您可以使用 CTE 插入第一个子项,其中 CTE 挂起插入父项的语句(反之亦然)。这会在同一语句中插入两行,从而产生类似于延迟约束验证的效果。

        3) 如果没有任何一个,您别无选择,只能在其中一个引用中允许空值,这样您就可以插入该行而无需进行依赖性检查。然后,您必须返回并使用对第二行的引用来更新 null。如果您使用此技术,您需要小心使系统的其余部分通过隐藏子引用列中所有带有 null 的行的视图来引用父表。

        在任何情况下,您的子级删除都同样复杂,因为您无法删除证明至少存在一个的子级,除非您首先更新父级以指向不会被删除的子级.

        当您要删除最后一个子节点时,您必须抛出错误或同时删除父节点。如果您不首先将父指针设置为 null(或延迟验证),则会自动发生错误。如果您确实推迟(或将子指针设置为 null),则可以删除子元素,然后也可以删除父元素。

        我多年来一直在研究这个问题,我观察了每个版本的 SQL Server 以缓解这个问题,因为它很常见。

        一旦有人有实用的解决方案,请发布!

        附:在从父级引用您的子证明行时,您需要使用复合键,或者使用触发器来确保提供证明的子行实际上认为该行是其父行。

        P.P.S 虽然如果您在同一个事务中同时执行插入和更新,那么系统的其余部分肯定永远不会看到 null ,但这依赖于可能失败的行为。约束的重点是确保逻辑故障不会使您的数据库处于无效状态。通过使用隐藏空值的视图保护表,任何非法行都将不可见。显然,您的插入逻辑必须考虑到这样的行可能存在但它无论如何都需要内部知识并且不需要知道其他任何东西的可能性。

        【讨论】:

          【解决方案5】:

          我遇到了这个问题,并在 Oracle rel.11.2.4 中实施了解决方案。

          1. 为了确保每个子节点都有父节点,我将典型的外键约束从子节点的 FK 应用到父节点的 PK。 -- 这里没有魔法
          2. 为确保每位家长至少有一个孩子,我做了如下操作:

          创建一个接受父 PK 的函数,并返回该 PK 的 COUNT 个子节点。 -- 我确保 NO_DATA_FOUND 异常返回 0。

          在父表上创建一个虚拟列CHILD_COUNT,并计算到函数结果。

          使用CHILD_COUNT > 0 的条件在CHILD_COUNT 虚拟列上创建一个可延迟的CHECK 约束

          它的工作原理如下:

          • 如果插入了父行但尚不存在子行。那么如果该行被提交,CHILD_COUNT > 0 CHECK 约束失败并且事务回滚。
          • 如果在同一个事务中插入了父行并且插入了相应的子行;然后在发出COMMIT 时满足所有完整性约束。
          • 如果插入与现有父行相对应的子行,则在 COMMIT 上重新计算 CHILD_COUNT 虚拟列,并且不会发生完整性违规。
          • 对父行的任何删除都必须级联到子行,否则在提交删除事务时,孤立的子行将违反 FOREIGN KEY 约束。 (像往常一样)
          • 对子行的任何删除都必须为每个父行保留至少一个子行,否则在事务提交时将违反CHILD_COUNT 检查约束。

          注意:如果 Oracle 在 rel.11.2.4 允许基于用户功能的CHECK constraints,我将不需要虚拟列。

          【讨论】:

            猜你喜欢
            • 2017-04-12
            • 2018-06-29
            • 2012-08-08
            • 1970-01-01
            • 1970-01-01
            • 2020-03-19
            • 1970-01-01
            • 2013-03-12
            • 2014-10-16
            相关资源
            最近更新 更多