Restrict foreign key relationship to rows of related subtypes

前端 未结 1 1272
刺人心
刺人心 2021-01-23 03:23

Overview: I am trying to represent several types of entities in a database, which have a number of basic fields in common, and then each has some additional fields that are not

1条回答
  •  悲哀的现实
    2021-01-23 03:45

    Simplify building on MATCH SIMPLE behavior of fk constraints

    If at least one column of multicolumn foreign constraint with default MATCH SIMPLE behaviour is NULL, the constraint is not enforced. You can build on that to largely simplify your design.

    CREATE SCHEMA test;
    
    CREATE TABLE test.status(
       status_id  integer PRIMARY KEY
      ,sub        bool NOT NULL DEFAULT FALSE  -- TRUE .. *can* be sub-status
      ,UNIQUE (sub, status_id)
    );
    
    CREATE TABLE test.entity(
       entity_id  integer PRIMARY KEY
      ,status_id  integer REFERENCES test.status  -- can reference all statuses
      ,sub        bool      -- see examples below
      ,additional_col1 text -- should be NULL for main entities
      ,additional_col2 text -- should be NULL for main entities
      ,FOREIGN KEY (sub, status_id) REFERENCES test.status(sub, status_id)
         MATCH SIMPLE ON UPDATE CASCADE  -- optionally enforce sub-status
    );
    

    It is very cheap to store some additional NULL columns (for main entities):

    • How much disk-space is needed to store a NULL value using postgresql DB?

    BTW, per documentation:

    If the refcolumn list is omitted, the primary key of the reftable is used.

    Demo-data:

    INSERT INTO test.status VALUES
      (1, TRUE)
    , (2, TRUE)
    , (3, FALSE);     -- not valid for sub-entities
    
    INSERT INTO test.entity(entity_id, status_id, sub) VALUES
      (11, 1, TRUE)   -- sub-entity (can be main, UPDATES to status.sub cascaded)
    , (13, 3, FALSE)  -- entity  (cannot be sub,  UPDATES to status.sub cascaded)
    , (14, 2, NULL)   -- entity  (can    be sub,  UPDATES to status.sub NOT cascaded)
    , (15, 3, NULL)   -- entity  (cannot be sub,  UPDATES to status.sub NOT cascaded)
    

    SQL Fiddle (including your tests).

    Alternative with single FK

    Another option would be to enter all combinations of (status_id, sub) into the status table (there can only be 2 per status_id) and only have a single fk constraint:

    CREATE TABLE test.status(
       status_id  integer
      ,sub        bool DEFAULT FALSE
      ,PRIMARY KEY (status_id, sub)
    );
    
    CREATE TABLE test.entity(
       entity_id  integer PRIMARY KEY
      ,status_id  integer NOT NULL  -- cannot be NULL in this case
      ,sub        bool NOT NULL     -- cannot be NULL in this case
      ,additional_col1 text
      ,additional_col2 text
      ,FOREIGN KEY (status_id, sub) REFERENCES test.status
         MATCH SIMPLE ON UPDATE CASCADE  -- optionally enforce sub-status
    );
    
    INSERT INTO test.status VALUES
      (1, TRUE)       -- can be sub ...
      (1, FALSE)      -- ... and main
    , (2, TRUE)
    , (2, FALSE)
    , (3, FALSE);     -- only main
    

    Etc.

    Related answers:

    • MATCH FULL vs MATCH SIMPLE
    • Two-column foreign key constraint only when third column is NOT NULL
    • Uniqueness validation in database when validation has a condition on another table

    Keep all tables

    If you need all four tables for some reason not in the question consider this detailed solution to a very similar question on dba.SE:

    • Enforcing constraints “two tables away”

    Inheritance

    ... might be another option for what you describe. If you can live with some major limitations. Related answer:

    • Create a table of two types in PostgreSQL

    0 讨论(0)
提交回复
热议问题