PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Как да създадете дървовидна таблица без циклична връзка?

Вероятно най-простата и най-разпространена SQL реализация на дърво е самореферентна таблица, напр.:

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.   



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. група по дата агрегатна функция в postgresql

  2. Използване на автоматизация за ускоряване на тестовете за издаване на PostgreSQL

  3. Връзка грешка не съществува

  4. Каква е причината за грешката Повече не се разпознава... при стартиране на Postgresql 11 на машина с Windows?

  5. SQL Намерете всички преки наследници в дърво