From: | Michael Glaesemann <grzm(at)myrealbox(dot)com> |
---|---|
To: | Michael Fuhr <mike(at)fuhr(dot)org> |
Cc: | David Fetter <david(at)fetter(dot)org>, PostgreSQL General <pgsql-general(at)postgresql(dot)org> |
Subject: | Re: Each foo must have a bar |
Date: | 2006-02-12 12:09:35 |
Message-ID: | BC96C81F-8C37-4026-80AD-4D1DFDD809F4@myrealbox.com |
Views: | Raw Message | Whole Thread | Download mbox | Resend email |
Thread: | |
Lists: | 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)
From | Date | Subject | |
---|---|---|---|
Next Message | Doug McNaught | 2006-02-12 13:57:41 | Re: Last modification time |
Previous Message | Joe Conway | 2006-02-12 07:08:01 | Re: Seeking comments on schema design and data integrity |