-- 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
;
Вывод:
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)
Кстати: я пропустил атрибуты.Я предлагаю подключить их к соответствующим категориям с помощью модели данных типа EAV.