1
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)
)

What constraint can i add to avoid cycle in the tree?

klin
  • 112,967
  • 15
  • 204
  • 232
Zanko
  • 4,298
  • 4
  • 31
  • 54
  • 2
    AFAIK, there is no type of constraint for that in any of the big classic dbms systems. You'd have to enforce this elsewhere, either in triggers or in the application or stored proc that manages this information. For more specific information, it may help to tag your question with the database system you're using. – GolezTrol Jan 10 '19 at 14:33
  • @GolezTrol thanks! I can enforce this in the application. From your experience, do you know an appropriate design for tree? – Zanko Jan 10 '19 at 14:35
  • *"An appropriate design for tree"* is pretty broad, it depends of the use case. – Cid Jan 10 '19 at 14:36

1 Answers1

4

Likely the simplest and most common SQL implementation of a tree is a self-referencing table, e.g.:

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);

You can walk the tree from top to bottom with a recursive query like this:

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)

See also this answer for a bottom-up example.

Integrity

You cannot remove a node that is the parent of another one. The foreign key prevents the tree from being divided into separate parts:

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".    

Optionally, you can ensure that the tree has only one root using a partial unique index:

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. 

Cycles

You can eliminate the possibility of entering cycles using a trigger. The function checks whether one of the ancestors of an inserted or updated node could be the node itself:

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();

Check:

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.   
klin
  • 112,967
  • 15
  • 204
  • 232