Kemungkinan implementasi SQL pohon yang paling sederhana dan paling umum adalah tabel referensi sendiri, misalnya:
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);
Anda dapat menelusuri pohon dari atas ke bawah dengan kueri rekursif seperti ini:
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)
Lihat juga jawaban ini untuk contoh dari bawah ke atas.
Integritas
Anda tidak dapat menghapus simpul yang merupakan induk dari simpul lain. Kunci asing mencegah pohon dibagi menjadi beberapa bagian terpisah:
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".
Secara opsional, Anda dapat memastikan bahwa pohon hanya memiliki satu akar menggunakan indeks unik parsial:
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.
Siklus
Anda dapat menghilangkan kemungkinan memasuki siklus menggunakan pemicu. Fungsi tersebut memeriksa apakah salah satu ancestor dari node yang dimasukkan atau diperbarui bisa menjadi node itu sendiri:
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();
Periksa:
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.