我们有两个具有一对多关系的表。我们希望强制执行一个约束,即给定父记录至少存在一个子记录。
这可能吗?
如果不是,您是否会将架构更改得更复杂一些以支持这样的约束?如果是这样,你会怎么做?
编辑:我正在使用 SQL Server 2005
我们有两个具有一对多关系的表。我们希望强制执行一个约束,即给定父记录至少存在一个子记录。
这可能吗?
如果不是,您是否会将架构更改得更复杂一些以支持这样的约束?如果是这样,你会怎么做?
编辑:我正在使用 SQL Server 2005
从模式的角度来看,这样的约束是不可能的,因为您遇到了“鸡或蛋”类型的场景。在这种情况下,当我插入父表时,我必须在子表中有一行,但在父表中有一行之前,我不能在子表中有一行。
这是更好地执行客户端的东西。
如果您的后端支持可延迟的约束,这是可能的,就像 PostgreSQL 一样。
一个简单的不可为空的列怎么样?
Create Table ParentTable
(
ParentID
ChildID not null,
Primary Key (ParentID),
Foreign Key (ChildID ) references Childtable (ChildID));
)
如果您的业务逻辑允许并且您具有可以从数据库中查询每个新父记录的默认值,那么您可以before insert trigger
在父表上使用 a 来填充不可为空的子列。
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;
这实际上并不是“在客户端更好地执行”,因为在某些数据库实现中执行它是不切实际的。实际上,该作业确实属于数据库,并且至少以下一种解决方法应该有效。
最终,您想要的是将父母限制在孩子身上。这保证了一个孩子的存在。不幸的是,这会导致鸡蛋问题,因为孩子必须指向同一个父母,从而导致约束冲突。
在系统的其余部分没有明显副作用的情况下解决问题需要两种能力之一——SQL Server 中没有这两种能力。
1) 延迟约束验证——这会导致在事务结束时验证约束。通常它们发生在语句的末尾。这是鸡蛋问题的根源,因为它阻止您插入第一个子行或父行,因为缺少另一个,这解决了它。
2) 您可以使用 CTE 插入第一个子项,其中 CTE 挂起插入父项的语句(反之亦然)。这会在同一语句中插入两行,从而产生类似于延迟约束验证的效果。
3)如果没有任何一个,您别无选择,只能在其中一个引用中允许空值,这样您就可以在不进行依赖检查的情况下插入该行。然后您必须返回并使用对第二行的引用来更新 null。如果您使用这种技术,您需要小心使系统的其余部分通过一个视图来引用父表,该视图在子引用列中隐藏所有带有 null 的行。
在任何情况下,您对子项的删除都同样复杂,因为您无法删除证明至少存在一个的子项,除非您首先更新父项以指向不会被删除的子项。
当您要删除最后一个子项时,您必须抛出错误或同时删除父项。如果您不首先将父指针设置为 null(或延迟验证),则会自动发生错误。如果您确实推迟(或将子指针设置为空),则可以删除子元素,然后也可以删除父元素。
我多年来一直在研究这个问题,我观察了每个版本的 SQL Server 以缓解这个问题,因为它很常见。
请一旦有人有实用的解决方案,请发布!
PS您需要在从父级引用您的子证明行时使用复合键,或者使用触发器以确保提供证明的子行实际上认为该行是其父行。
PPS 尽管如果您在同一个事务中同时执行插入和更新,那么系统的其余部分绝对不应该看到 null ,但这依赖于可能失败的行为。约束的重点是确保逻辑故障不会使您的数据库处于无效状态。通过使用隐藏空值的视图保护表,任何非法行都将不可见。显然,您的插入逻辑必须考虑到这样的行可能存在的可能性,但无论如何它都需要内部知识,并且不需要知道其他任何信息。
我遇到了这个问题,并在 Oracle rel.11.2.4 中实施了一个解决方案。
创建一个接受父 PK 的函数,并返回该 PK 的 COUNT 个子节点。-- 我确保 NO_DATA_FOUND 异常返回 0。
在父表上创建一个虚拟列CHILD_COUNT
,计算到函数结果。
CHILD_COUNT
使用以下条件在虚拟列上创建可延迟的 CHECK 约束CHILD_COUNT > 0
它的工作原理如下:
CHILD_COUNT > 0
CHECK 约束失败并且事务回滚。COMMIT
。CHILD_COUNT
重新计算虚拟列COMMIT
并且不会发生完整性违规。CHILD_COUNT
在事务提交时将违反检查约束。CHECK constraints
注意:如果 Oracle在 rel.11.2.4允许基于用户功能,我将不需要虚拟列。