Reputation: 7944
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
Upvotes: 4
Views: 5030
Reputation: 391
I am encountering this issue, and have a solution implemented in Oracle rel.11.2.4.
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:
CHILD_COUNT > 0
CHECK constraint fails and the transaction rolls back.COMMIT
is issued.CHILD_COUNT
virtual column is recalculated on COMMIT
and no integrity violation occurs.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.
Upvotes: 1
Reputation: 1702
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!
P.S. 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.
P.P.S 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.
Upvotes: 0
Reputation: 17132
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;
Upvotes: 0
Reputation: 16321
It's possible if your back-end supports deferrable constraints, as does PostgreSQL.
Upvotes: 1
Reputation: 185613
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.
Upvotes: 8