Re: Each foo must have a bar - Mailing list pgsql-general

From Michael Glaesemann
Subject Re: Each foo must have a bar
Date
Msg-id BC96C81F-8C37-4026-80AD-4D1DFDD809F4@myrealbox.com
Whole thread Raw
In response to Re: Each foo must have a bar  (Michael Fuhr <mike@fuhr.org>)
List pgsql-general
I haven't seen this hit the lists yet, though I sent it nearly 12
hours ago. Resending for completeness.

On Feb 12, 2006, at 6:59 , Michael Fuhr wrote:

> On Sat, Feb 11, 2006 at 12:56:36PM -0800, David Fetter wrote:
>
>> I'm trying to figure out how to enforce the following.  Table foo has
>> a primary key.  Table bar has a foreign key to foo.  So far so good.
>> I'd also like to say, "for each row in foo, there must be at least
>> one
>> row in bar."
>>
>
> Possibly something involving CREATE CONSTRAINT TRIGGER, specifying
> DEFERRABLE INITIALLY DEFERRED?  The documentation says it's not for
> general use; I've used it only in simple experiments so I'm not
> sure how problematic it could be.  Anybody?
>

I've used constraint triggers to handle multi-statement updates for
temporal tables. I also haven't had any in production but am planning
to soon. I have tested it quite a bit and haven't seen any problems.
Caveat being that I tend to have small databases, so I'm not sure
about the performance of the PL/pgSQL function I use to enforce the
assertion. I suspect the except should be more performant than the
count, but that's speculation.

I don't know if the SQL spec allows for statements with multiple
updates, e.g. something like

insert into foo (foo) values ('blurfl'), -- note comma
insert into bar (bar, foo_id)
select 'bat', foo_id
from foo where id = 'blurfl';

(And I definitely don't know if that kind of recursive assignment
would even work; maybe it would have to be more like:

insert into foo (foo_id, foo) values (1, 'blurfl'),
insert into bar (bar, foo_id) values ('bat', 1);
)

That might help get rid of the need to use a constraint trigger
rather than a normal assertion. Not that PostgreSQL has assertions
yet anyway :)

Please find an example below. Hope this helps.

Michael Glaesemann
grzm myrealbox com

create table foo
(
     foo_id serial primary key
     , foo text not null unique
);

create table bar
(
     bar_id serial primary key
     , bar text not null unique
     , foo_id integer not null
         references foo (foo_id)
         on update cascade on delete cascade
);

create function assert_bar_for_each_foo()
returns trigger
language plpgsql as $$
begin
     if exists (
         select foo_id
         from foo
         except
         select foo_id
         from bar
     )
     then raise exception 'Every foo must have a bar';
     end if;
     return null;
end;
$$;

create constraint trigger assert_bar_for_each_foo_tr
after insert on foo
-- With the on update on delete cascade I don't believe you
-- need to check on update or delete here.
initially deferred
for each row
execute procedure assert_bar_for_each_foo();

create constraint trigger assert_bar_for_each_foo_tr
after delete on bar
-- The foreign key on bar takes care of inserts and updates.
initially deferred
for each row
execute procedure assert_bar_for_each_foo();

insert into foo (foo) values ('blurfl');
ERROR:  Every foo must have a bar

begin;
insert into foo (foo) values ('blurfl');
INSERT 0 1
insert into bar (bar, foo_id)
INSERT 0 1
select 'bat', foo_id
from foo
where foo = 'blurfl';
commit;

select *
from foo
natural join bar;
foo_id |  foo   | bar_id | bar
--------+--------+--------+-----
       2 | blurfl |      1 | bat
(1 row)

update foo
set foo_id = 3
where foo = 'blurfl';
UPDATE 1

select *
from foo
natural join bar;
foo_id |  foo   | bar_id | bar
--------+--------+--------+-----
       3 | blurfl |      1 | bat
(1 row)

update bar
set foo_id = 2
where bar = 'bat';
ERROR:  insert or update on table "bar" violates foreign key
constraint "bar_foo_id_fkey"
DETAIL:  Key (foo_id)=(2) is not present in table "foo".

delete from bar where bar = 'bat';
ERROR:  Every foo must have a bar

delete from foo where foo = 'blurfl';
DELETE 1

select * from foo;
foo_id | foo
--------+-----
(0 rows)

select * from bar;

bar_id | bar | foo_id
--------+-----+--------
(0 rows)

select version();

version
------------------------------------------------------------------------
----------------------------------------------------------------------
PostgreSQL 8.1.0 on powerpc-apple-darwin8.3.0, compiled by GCC
powerpc-apple-darwin8-gcc-4.0.0 (GCC) 4.0.0 (Apple Computer, Inc.
build 5026)
(1 row)



pgsql-general by date:

Previous
From: Joe Conway
Date:
Subject: Re: Seeking comments on schema design and data integrity
Next
From: Doug McNaught
Date:
Subject: Re: Last modification time