简体   繁体   中英

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

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.

1) Deferred constraint validation - This causes constraints to be validated at the end the transaction. 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). 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. Then you must go back and update the null with the reference to the second row. 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.

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). 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.

I literally researched this for years and I watch every version of SQL Server for relief from this problem since it's so common.

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.

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. 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.

  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. -- 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. -- I ensure that NO_DATA_FOUND exceptions return 0.

Create a virtual column CHILD_COUNT on the parent table and calculate it to the function result.

Create a deferrable CHECK constraint on the CHILD_COUNT virtual column with the criteria of 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.
  • 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.
  • 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.
  • 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. (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.

NOTE: that I would not need a virtual column if Oracle would allow user-function-based CHECK constraints at rel.11.2.4.

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.

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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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