对一对多关系的约束

时间:2009-04-15 14:41:17

标签: sql sql-server database-design constraints

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

这可能吗?

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

编辑:我正在使用SQL Server 2005

5 个答案:

答案 0 :(得分:7)

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

这是更好的强制客户端。

答案 1 :(得分:1)

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

答案 2 :(得分:0)

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

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;

答案 3 :(得分:0)

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

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

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

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

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

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

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

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

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

只要有人有实用的解决方案,请发帖!

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

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

答案 4 :(得分:0)

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

  1. 为了确保每个孩子都有父母,我将一个典型的外键约束从孩子的FK应用到父母的PK。 - 这里没有巫术
  2. 为了确保每位家长至少有一个孩子,我做了如下:
  3. 创建一个接受父PK的函数,并为该PK返回COUNT个子节点。 - 我确保NO_DATA_FOUND异常返回0。

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

    使用CHILD_COUNT

    标准在CHILD_COUNT > 0虚拟列上创建可延迟的CHECK约束

    它的工作原理如下:

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

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