繁体   English   中英

对一对多关系的约束

[英]Constraint for one-to-many relationship

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

这可能吗?

如果没有,您是否会更改模式以支持此类约束? 如果是这样你会怎么做?

编辑:我正在使用SQL Server 2005

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

这是更好的强制客户端。

如果您的后端支持可延迟约束,PostgreSQL也是如此。

这实际上并不是“在客户端更好地执行”,而是在某些数据库实现中强制实施的东西。 实际上,作业属于数据库,并且下面的解决方法中至少有一个应该有效。

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

在系统的其余部分中解决问题而没有可见的副作用需要两种能力中的一种 - 在SQL Server中都找不到这两种能力。

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

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

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

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

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

我多年来一直在研究这个问题,我看每个版本的SQL Server都可以解决这个问题,因为它很常见。

尽快有人提供实用的解决方案,请发布!

PS您需要在引用来自父项的子证明行时使用复合键,或者触发以确保提供证据的孩子实际上认为该行是其父项。

PPS虽然如果你在同一个事务中同时执行插入和更新,那么对于系统的其余部分来说,null永远不应该是可见的,这依赖于可能失败的行为。 约束的关键是确保逻辑故障不会使数据库处于无效状态。 通过使用隐藏空值的视图保护表,任何非法行都将不可见。 很明显,你的插入逻辑必须考虑到这样一行可能存在的可能性,但无论如何它都需要内部知识,而其他任何事情都不需要知道。

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

  1. 为了确保每个孩子都有父母,我将一个典型的外键约束从孩子的FK应用到父母的PK。 - 这里没有巫术
  2. 为了确保每个父母至少有一个孩子,我做了如下:

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

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

CHILD_COUNT虚拟列上创建可延迟的CHECK约束,条件为CHILD_COUNT > 0

它的工作原理如下:

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

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

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

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;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM