I am drawn to this design like a moth to a flame. I've read rumblings that cyclic foreign keys are a nightmare. Generally, I can see why they should be avoided. In this particular case, however, I don't see why it would be so awful. Got some inspiration from this article, but combined the reverse-belongs-to and exclusive-belongs-to designs into one... monstrous creation?
Can you tell me why (or if) this is a bad idea? Like what headaches am I going to get into with this and are they worth it?
Anyway, I want to create a polymorphic relationship between many entities and one table. I want to make something like this:
-- poly ids are uuids and we assume they will not collide, period
create domain poly_id uuid;
create table foo ([...table columns], poly_id not null default gen_random_uuid())
create table bar ([...table columns], poly_id not null default gen_random_uuid())
create table baz ([...table columns], poly_id not null default gen_random_uuid())
create type poly_t as enum ('foo', 'bar', 'baz')
create table poly_obj (
-- poly_id is always the poly_id of the one set reference column
poly_id poly_id not null
generated always as ( coalesce("foo", "bar", "baz") ),
poly_t poly_t not null,
"foo" poly_id null references foo (poly_id) check ( "foo" is null or poly_t = 'foo' ),
"bar" poly_id null references bar (poly_id) check ( "bar" is null or poly_t = 'bar' ),
"baz" poly_id null references baz (poly_id) check ( "baz" is null or poly_t = 'baz' )
-- only one fk to child table can be set
check (
(
("foo" is not null)::integer +
("bar" is not null)::integer +
("baz" is not null)::integer
) = 1
)
)
create unique index on poly_obj ("foo") where "foo" is not null;
create unique index on poly_obj ("bar") where "bar" is not null;
create unique index on poly_obj ("baz") where "baz" is not null;
alter table foo add foreign key (poly_id) references poly_obj (poly_id)
alter table bar add foreign key (poly_id) references poly_obj (poly_id)
alter table baz add foreign key (poly_id) references poly_obj (poly_id)
-- more pseudocodey than the rest
create trigger ___ after insert on foo for each row execute
insert into poly_obj (poly_t, foo) select ('foo', poly_id) from new;
create trigger ___ after insert on bar for each row execute
insert into poly_obj (poly_t, bar) select ('bar', poly_id) from new;
create trigger ___ after insert on baz for each row execute
insert into poly_obj (poly_t, baz) select ('baz', poly_id) from new;
My use case is wanting (ideally) 2-way referential integrity to refer to rows in N tables across my schemas. I've gotten an implementation that's not too dissimilar to what I posted working. My app's scale is decidedly medium in any anticipateable term, so I'm trying to go for maximum semantic content in my schema rather than max performance. Does your design support 2 way references between multiple tables?
– untitled90 Aug 10 '23 at 16:56