Как создать древовидную таблицу без циклических связей? - PullRequest
0 голосов
/ 10 января 2019
CREATE TABLE TREE  (
  node1_id      UUID REFERENCES  nodes (object_id) NOT NULL,
  node2_id      UUID REFERENCES  nodes(object_id) NOT NULL,
  CONSTRAINT node2_owned_constraint UNIQUE (node2_id),
  CONSTRAINT invalid_tree_constraint CHECK (node1_id!= node2_id)
)

Какое ограничение я могу добавить, чтобы избежать цикла в дереве?

1 Ответ

0 голосов
/ 10 января 2019

Вероятно, самой простой и распространенной реализацией дерева SQL является таблица с самоссылкой, например ::10000

create table tree(
    id int primary key, 
    parent int references tree(id));

insert into tree values
    (1, null),
    (2, 1),
    (3, 1),
    (4, 2),
    (5, 4);

Вы можете пройтись по дереву сверху вниз с помощью рекурсивного запроса, подобного этому:

with recursive top_down as (
    select id, parent, array[id] as path
    from tree
    where parent is null
union all
    select t.id, t.parent, path || t.id
    from tree t
    join top_down r on t.parent = r.id
)
select *
from top_down;

 id | parent |   path    
----+--------+-----------
  1 |        | {1}
  2 |      1 | {1,2}
  3 |      1 | {1,3}
  4 |      2 | {1,2,4}
  5 |      4 | {1,2,4,5}
(5 rows)

См. Также этот ответ для восходящего примера.

Целостность

Вы не можете удалить узел, который является родительским для другого. Внешний ключ предотвращает разделение дерева на отдельные части:

delete from tree
where id = 2;

ERROR:  update or delete on table "tree" violates foreign key constraint "tree_parent_fkey" on table "tree"
DETAIL:  Key (id)=(2) is still referenced from table "tree".    

При желании вы можете убедиться, что дерево имеет только один корень, используя частичный уникальный индекс:

create unique index tree_one_root_idx on tree ((parent is null)) where parent is null;

insert into tree
values(6, null);

ERROR:  duplicate key value violates unique constraint "tree_one_root_idx"
DETAIL:  Key ((parent IS NULL))=(t) already exists. 

Циклы

Вы можете исключить возможность ввода циклов с помощью триггера. Функция проверяет, может ли один из предков вставленного или обновленного узла быть самим узлом:

create or replace function before_insert_or_update_on_tree()
returns trigger language plpgsql as $$
declare rec record;
begin
    if exists(
        with recursive bottom_up as (
            select new.id, new.parent, array[]::int[] as path, false as cycle
        union all
            select r.id, t.parent, path || t.id, new.id = any(path)
            from tree t
            join bottom_up r on r.parent = t.id and not cycle
        )
        select *
        from bottom_up
        where cycle or (id = parent))
    then raise exception 'Cycle detected on node %.', new.id;
    end if;
    return new;
end $$;

create trigger before_insert_or_update_on_tree
before insert or update on tree
for each row execute procedure before_insert_or_update_on_tree();

Проверка:

insert into tree values (6, 7), (7, 6);

ERROR:  Cycle detected on node 7.

update tree
set parent = 4
where id = 2;

ERROR:  Cycle detected on node 2.   
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...