What is the best way to enforce a 'subset' relationship with integrity constraints

前端 未结 12 1325
一个人的身影
一个人的身影 2020-12-10 18:11

For example, given 3 tables:

  • gastropod
  • snail
  • slug

and assuming we want to enforce that

  1. every row in \'gastropod\
12条回答
  •  时光取名叫无心
    2020-12-10 18:39

    My own solution for postgres (but I have no idea if it is the best way):

    enum:

    create type gastropod_type as enum ('slug', 'snail');
    

    tables and constraints:

    create table gastropod(
      gastropod_id serial unique,
      gastropod_type gastropod_type,
      slug_gastropod_id integer,
      snail_gastropod_id integer,
      average_length numeric,
      primary key(gastropod_id, gastropod_type),
      check( (case when slug_gastropod_id is null then 0 else 1 end)+
             (case when snail_gastropod_id is null then 0 else 1 end)=1) );
    
    create table slug(
      gastropod_id integer unique,
      gastropod_type gastropod_type check (gastropod_type='slug'),
      is_mantle_visible boolean,
      primary key(gastropod_id, gastropod_type),
      foreign key(gastropod_id, gastropod_type) 
        references gastropod deferrable initially deferred );
    
    create table snail(
      gastropod_id integer unique,
      gastropod_type gastropod_type check (gastropod_type='snail'),
      average_shell_volume numeric,
      primary key(gastropod_id, gastropod_type),
      foreign key(gastropod_id, gastropod_type)
        references gastropod deferrable initially deferred );
    
    alter table gastropod 
    add foreign key(slug_gastropod_id, gastropod_type) 
    references slug deferrable initially deferred;
    
    alter table gastropod 
    add foreign key(snail_gastropod_id, gastropod_type) 
    references snail deferrable initially deferred;
    

    test:

    insert into gastropod(gastropod_type, slug_gastropod_id, average_length)
    values ('slug', currval('gastropod_gastropod_id_seq'), 100);
    
    insert into slug(gastropod_id, gastropod_type, is_mantle_visible)
    values (currval('gastropod_gastropod_id_seq'), 'slug', true);
    
    select gastropod_id, gastropod_type, average_length, is_mantle_visible
    from gastropod left outer join slug using(gastropod_id, gastropod_type) 
                   left outer join snail using(gastropod_id, gastropod_type);
    
     gastropod_id | gastropod_type | average_length | is_mantle_visible
    --------------+----------------+----------------+-------------------
                1 | slug           |            100 | t                 
    (1 row)
    

提交回复
热议问题