简体   繁体   English

对一对多关系的约束

[英]Constraint for one-to-many relationship

We have a two tables with a one-to-many relationship. 我们有两个表与一对多的关系。 We would like to enforce a constraint that at least one child record exist for a given parent record. 我们希望强制执行约束,即给定父记录至少存在一个子记录。

Is this possible? 这可能吗?

If not, would you change the schema a bit more complex to support such a constraint? 如果没有,您是否会更改模式以支持此类约束? If so how would you do it? 如果是这样你会怎么做?

Edit: I'm using SQL Server 2005 编辑:我正在使用SQL Server 2005

Such a constraint isn't possible from a schema perspective, because you run into a "chicken or the egg" type of scenario. 从架构的角度来看,这样的约束是不可能的,因为你遇到了“鸡或鸡蛋”类型的场景。 Under this sort of scenario, when I insert into the parent table I have to have a row in the child table, but I can't have a row in the child table until there's a row in the parent table. 在这种情况下,当我插入父表时,我必须在子表中有一行,但是在父表中有一行之前,我不能在子表中有一行。

This is something better enforced client-side. 这是更好的强制客户端。

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

This isn't really something 'better enforced on the client side' so much as it is something that is impractical to enforce within certain database implementations. 这实际上并不是“在客户端更好地执行”,而是在某些数据库实现中强制实施的东西。 Realistically the job DOES belong in the database and at least one of the workarounds below should work. 实际上,作业属于数据库,并且下面的解决方法中至少有一个应该有效。

Ultimately what you want is to constrain the parent to a child. 最终你想要的是将父母约束为孩子。 This guarantees that a child exists. 这保证了孩子的存在。 Unfortunately this causes a chicken-egg problem because the children must point to the same parent causing a constraint conflict. 不幸的是,这会导致鸡蛋问题,因为孩子必须指向导致约束冲突的同一父母。

Getting around the problem without visible side-effects in the rest of your system requires one of two abilities - neither of which is found in SQL Server. 在系统的其余部分中解决问题而没有可见的副作用需要两种能力中的一种 - 在SQL Server中都找不到这两种能力。

1) Deferred constraint validation - This causes constraints to be validated at the end the transaction. 1)延迟约束验证 - 这会导致在事务结束时验证约束。 Normally they happen at the end of a statement. 通常它们发生在声明的最后。 This is the root of the chicken-egg problem since it prevents you from inserting either the first child or the parent row for lack of the other and this resolves it. 这是鸡蛋问题的根源,因为它会阻止你插入第一个孩子或父行而缺少另一个,这就解决了它。

2) You can use a CTE to insert the first child where the CTE hangs off of the statement that inserts the parent (or vise versa). 2)您可以使用CTE插入CTE挂起插入父节点的语句的第一个子节点(反之亦然)。 This inserts both rows in the same statement causing an effect similar to deferred constraint validation. 这会在同一语句中插入两行,从而产生类似于延迟约束验证的效果。

3) Without either you have no choice but to allow nulls in one of the references so you can insert that row without the dependency check. 3)没有任何一个,你别无选择,只能在其中一个引用中允许空值,这样你就可以在没有依赖性检查的情况下插入该行。 Then you must go back and update the null with the reference to the second row. 然后,您必须返回并使用对第二行的引用更新null。 If you use this technique you need to be careful to make the rest of the system refer to the parent table thru a view that hides all rows with null in the child reference column. 如果您使用此技术,则需要注意使系统的其余部分通过一个视图来引用父表,该视图在子引用列中隐藏所有具有null的行。

In any case your deletes of children are just as complicated because you cannot delete the child that proves at least one exists unless you update the parent first to point to a child that won't be deleted. 在任何情况下,您删除的子项都同样复杂,因为您无法删除证明至少存在一个子项的子项,除非您首先更新父项以指向不会被删除的子项。

When you are about to delete the last child either you must throw an error or delete the parent at the same time. 当您要删除最后一个子项时,您必须同时抛出错误或删除父项。 The error will occur automatically if you don't set the parent pointer to null first (or defer validation). 如果您没有先将父指针设置为null(或延迟验证),则会自动发生错误。 If you do defer (or set the child pointer to null) your delete of the child will be possible and the parent can then be deleted as well. 如果您确实推迟(或将子指针设置为null),则可以删除子项,然后也可以删除父项。

I literally researched this for years and I watch every version of SQL Server for relief from this problem since it's so common. 我多年来一直在研究这个问题,我看每个版本的SQL Server都可以解决这个问题,因为它很常见。

PLEASE As soon as anyone has a practical solution please post! 尽快有人提供实用的解决方案,请发布!

PS You need to either use a compound key when referring to your proof-of-child row from the parent or a trigger to insure that the child providing proof actually considers that row to be its parent. PS您需要在引用来自父项的子证明行时使用复合键,或者触发以确保提供证据的孩子实际上认为该行是其父项。

PPS Although it's true that null should never be visible to the rest of your system if you do both inserts and the update in the same transaction this relies on behavior that could fail. PPS虽然如果你在同一个事务中同时执行插入和更新,那么对于系统的其余部分来说,null永远不应该是可见的,这依赖于可能失败的行为。 The point of the constraint is to insure that a logic failure won't leave your database in an invalid state. 约束的关键是确保逻辑故障不会使数据库处于无效状态。 By protecting the table with a view that hides nulls any illegal row will not be visible. 通过使用隐藏空值的视图保护表,任何非法行都将不可见。 Clearly your insert logic must account for the possibility that such a row can exist but it needs inside knowledge anyway and nothing else needs to know. 很明显,你的插入逻辑必须考虑到这样一行可能存在的可能性,但无论如何它都需要内部知识,而其他任何事情都不需要知道。

I am encountering this issue, and have a solution implemented in Oracle rel.11.2.4. 我遇到了这个问题,并在Oracle rel.11.2.4中实现了一个解决方案。

  1. To ensure that every child has a parent, I applied a typical foreign-key constraint from the FK of the child to the PK of the parent. 为了确保每个孩子都有父母,我将一个典型的外键约束从孩子的FK应用到父母的PK。 -- no wizardry here - 这里没有巫术
  2. To ensure that every parent has at least one child, I did as follows: 为了确保每个父母至少有一个孩子,我做了如下:

Create a function which accepts a parent PK, and returns a COUNT of children for that PK. 创建一个接受父PK的函数,并为该PK返回COUNT个子项。 -- I ensure that NO_DATA_FOUND exceptions return 0. - 我确保NO_DATA_FOUND异常返回0。

Create a virtual column CHILD_COUNT on the parent table and calculate it to the function result. 在父表上创建一个虚拟列CHILD_COUNT ,并将其计算为函数结果。

Create a deferrable CHECK constraint on the CHILD_COUNT virtual column with the criteria of CHILD_COUNT > 0 CHILD_COUNT虚拟列上创建可延迟的CHECK约束,条件为CHILD_COUNT > 0

It works as follows: 它的工作原理如下:

  • If a parent row is inserted and no children exist yet. 如果插入父行且尚未存在子项。 then if that row is committed, the CHILD_COUNT > 0 CHECK constraint fails and the transaction rolls back. 然后,如果该行已提交,则CHILD_COUNT > 0 CHECK约束将失败,并且事务将回滚。
  • If a parent row is inserted and a corresponding child row is inserted in the same transaction; 如果插入父行并在同一事务中插入相应的子行; then all integrity constraints are satisfied when a COMMIT is issued. 然后在发出COMMIT时满足所有完整性约束。
  • If a child row is inserted corresponding to an existing parent row, then the CHILD_COUNT virtual column is recalculated on COMMIT and no integrity violation occurs. 如果插入对应于现有父行的子行,则在COMMIT上重新计算CHILD_COUNT虚拟列,并且不会发生完整性冲突。
  • Any deletes of parent rows must cascade to the children otherwise orphaned child rows will violate the FOREIGN KEY constraint when the delete transaction is committed. 父行的任何删除都必须级联到子级,否则在提交删除事务时,孤立的子行将违反FOREIGN KEY约束。 (as usual) (照常)
  • Any deletes of child rows must leave at least one child for each parent otherwise the CHILD_COUNT check constraint will violate when the transaction commits. 子行的任何删除都必须为每个父项留下至少一个子项,否则在事务提交时将违反CHILD_COUNT检查约束。

NOTE: that I would not need a virtual column if Oracle would allow user-function-based CHECK constraints at rel.11.2.4. 注意:如果Oracle在rel.11.2.4中允许基于用户功能的CHECK constraints ,那么我就不需要虚拟列。

How about a simple non nullable column? 一个简单的非可空列怎么样?

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

If your business logic allows and you have default values you can query from the database for each new parent record, you can then use a before insert trigger on the parent table to populate the non nullable child column. 如果您的业务逻辑允许并且您具有默认值,则可以从数据库中查询每个新父记录,然后可以在父表上使用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