Question

If a database has a pair of tables in a typical "parent and child" fashion, is there a way to enforce (without using triggers) that each parent can only have a maximum, say, of four children?

So we have a Parents table:

create table dbo.Parents (
    ParentID char(2) not null primary key
    /* Yada Yada Yada */
)

And the Children table:

create table dbo.Children (
    ChildID char(2) not null primary key,
    ParentID char(2) not null references dbo.Parents (ParentID)
    /* usw */
)

And we populate the Parents table:

insert into Parents (ParentID) values ('aa'),('bb')

I want this insert to succeed:

insert into Children (ChildID,ParentID) values
('a1','aa'),('a2','aa'),('a3','aa')

But for this insert to fail:

insert into Children (ChildID,ParentID) values
('b1','bb'),('b2','bb'),('b3','bb'),('b4','bb'),('b5','bb')
Was it helpful?

Solution

There is a way to do this without using triggers, but it's ugly code and I'm not sure if I'd ever recommend using it in anger.

This technique uses an indexed view to enforce the constraint. Normally, I'm perfectly happy to use indexed views to enforce constraints, certainly over and above using triggers to enforce constraints. But the issue here is that the error message that is produced when the constraint is violated makes no mention of any constraint names - you'll get an error message and have to know that the error message has been produced by a "booby-trap" in your database.

But here, for your viewing pleasure, is the view:

create view dbo.DRI_Parent_Child_Maximum4
with schemabinding
as
    select ParentID,COUNT_BIG(*) as Cnt,
           SUM(536870911) as Meaningless
    from dbo.Children
    group by ParentID
go
create unique clustered index IX_DRI_Parent_Child_Maximum4
on dbo.DRI_Parent_Child_Maximum4(ParentID)

And the error message it produces for the second insert:

Msg 8115, Level 16, State 2, Line 1
Arithmetic overflow error converting expression to data type int.
The statement has been terminated.

In case it's not obvious, this works by arranging for 4 * some magic number to be less than the maximum number that can be represented by an int, but 5 * that same magic number is higher than the maximum.

And so if there are 4 or less rows, the SUM() works, but if you attempt to have 5 or more rows, we get the error.


This technique could even be extended to having the maximum number of children allowed be separately configured for each parent. You'd store the maximum in the Parents table and also have a look-up table that, for each possible maximum, stored a usable magic number to use to enforce that maximum.


I worked out how to do this yesterday, and despite being sure that there are questions that ask this from time to time on SO, I couldn't find any instances, hence the new question & answer. As I say at the top of this answer though, I'm not sure if I'd actually do this in production code, or whether to just keep it around as a cute trick.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top