Interesting tree/hierarchical data structure problem

后端 未结 6 904
無奈伤痛
無奈伤痛 2020-12-16 05:24

Colleges have different ways of organizing their departments. Some schools go School -> Term -> Department. Others have steps in between, with the longe

6条回答
  •  清歌不尽
    2020-12-16 05:54

    -- Enforcing a taxonomy by self-referential (recursive) tables.
    -- Both the classes and the instances have a recursive structure.
    -- The taxonomy is enforced mostly based on constraints on the classes,
    -- the instances only need to check that {their_class , parents_class} 
    -- form a valid pair.
    --
    DROP schema school CASCADE;
    CREATE schema school;
    
    CREATE TABLE school.category
      ( id INTEGER NOT NULL PRIMARY KEY
      , category_name VARCHAR
      );
    INSERT INTO school.category(id, category_name) VALUES
      ( 1, 'School' )
      , ( 2, 'Sub_campus' )
      , ( 3, 'Program' )
      , ( 4, 'Term' )
      , ( 5, 'Division' )
      , ( 6, 'Department' )
      ;
    
    -- This table contains a list of all allowable {child->parent} pairs.
    -- As a convention, the "roots" of the trees point to themselves.
    -- (this also avoids a NULL FK)
    CREATE TABLE school.category_valid_parent
      ( category_id INTEGER NOT NULL REFERENCES school.category (id)
      , parent_category_id INTEGER NOT NULL REFERENCES school.category (id)
      );
    ALTER TABLE school.category_valid_parent
      ADD PRIMARY KEY (category_id, parent_category_id)
      ;
    
    INSERT INTO school.category_valid_parent(category_id, parent_category_id)
      VALUES
      ( 1,1) -- school -> school
      , (2,1) -- subcampus -> school
      , (3,1) -- program -> school
      , (3,2) -- program -> subcampus
      , (4,1) -- term -> school
      , (4,2) -- term -> subcampus
      , (4,3) -- term -> program
      , (5,4) -- division --> term
      , (6,4) -- department --> term
      , (6,5) -- department --> division
      ;
    
    CREATE TABLE school.instance
      ( id INTEGER NOT NULL PRIMARY KEY
      , category_id INTEGER NOT NULL REFERENCES school.category (id)
      , parent_id INTEGER NOT NULL REFERENCES school.instance (id)
      -- NOTE: parent_category_id is logically redundant
      -- , but needed to maintain the constraint
      -- (without referencing a third table)
      , parent_category_id INTEGER NOT NULL REFERENCES school.category (id)
      , instance_name VARCHAR
      );      -- Forbid illegal combinations of {parent_id, parent_category_id}
    ALTER TABLE school.instance ADD CONSTRAINT valid_cat UNIQUE (id,category_id);
    ALTER TABLE school.instance
      ADD FOREIGN KEY (parent_id, parent_category_id)
          REFERENCES school.instance(id, category_id);
      ;
      -- Forbid illegal combinations of {category_id, parent_category_id}
    ALTER TABLE school.instance
      ADD FOREIGN KEY (category_id, parent_category_id) 
          REFERENCES school.category_valid_parent(category_id, parent_category_id);
      ;
    
    INSERT INTO school.instance(id, category_id
        , parent_id, parent_category_id
        , instance_name) VALUES
      -- Zulo
      (1,1,1,1, 'University of Utrecht' )
      , (2,2,1,1, 'Uithof' )
      , (3,3,2,2, 'Life sciences' )
      , (4,4,3,3, 'Bacherlor' )
      , (5,5,4,4, 'Biology' )
      , (6,6,5,5, 'Evolutionary Biology' )
      , (7,6,5,5, 'Botany' )
      -- Nulo
      , (11,1,11,1, 'Hogeschool Utrecht' )
      , (12,4,11,1, 'Journalistiek' )
      , (13,6,12,4, 'Begrijpend Lezen' )
      , (14,6,12,4, 'Typvaardigheid' )
      ;
    
      -- try to insert an invalid instance
    INSERT INTO school.instance(id, category_id
        , parent_id, parent_category_id
        , instance_name) VALUES
      ( 15, 6, 3,3, 'Procreation' );
    
    WITH RECURSIVE re AS (
      SELECT i0.parent_id AS pa_id
      , i0.parent_category_id AS pa_cat
      , i0.id AS my_id
      , i0.category_id AS my_cat
      FROM school.instance i0
      WHERE i0.parent_id = i0.id
      UNION
      SELECT i1.parent_id AS pa_id
      , i1.parent_category_id AS pa_cat
      , i1.id AS my_id
      , i1.category_id AS my_cat
      FROM school.instance i1
      , re
      WHERE re.my_id = i1.parent_id
      )
    SELECT re.*
      , ca.category_name
      , ins.instance_name
      FROM re
      JOIN school.category ca ON (re.my_cat = ca.id)
      JOIN school.instance ins ON (re.my_id = ins.id)
      -- WHERE re.my_id = 14
      ;
    

    The output:

    INSERT 0 11
    ERROR:  insert or update on table "instance" violates foreign key constraint "instance_category_id_fkey1"
    DETAIL:  Key (category_id, parent_category_id)=(6, 3) is not present in table "category_valid_parent".
     pa_id | pa_cat | my_id | my_cat | category_name |     instance_name 
    -------+--------+-------+--------+---------------+-----------------------
         1 |      1 |     1 |      1 | School        | University of Utrecht
        11 |      1 |    11 |      1 | School        | Hogeschool Utrecht
         1 |      1 |     2 |      2 | Sub_campus    | Uithof
        11 |      1 |    12 |      4 | Term          | Journalistiek
         2 |      2 |     3 |      3 | Program       | Life sciences
        12 |      4 |    13 |      6 | Department    | Begrijpend Lezen
        12 |      4 |    14 |      6 | Department    | Typvaardigheid
         3 |      3 |     4 |      4 | Term          | Bacherlor
         4 |      4 |     5 |      5 | Division      | Biology
         5 |      5 |     6 |      6 | Department    | Evolutionary Biology
         5 |      5 |     7 |      6 | Department    | Botany
    (11 rows)
    

    BTW: I left out the attributes. I propose they could be hooked to the relevant categories by means of a EAV type of data model.

提交回复
热议问题