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

    必要に応じて、部分的に一意のインデックスを使用して、ツリーにルートが1つしかないことを確認できます。

    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. 
    

    サイクル

    トリガーを使用して、サイクルに入る可能性を排除できます。この関数は、挿入または更新されたノードの祖先の1つがノード自体である可能性があるかどうかを確認します。

    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. データベースでのユーザーアクセスを制限することが重要な理由

    2. タイムスタンプ列をJPAタイプにマップする方法は?

    3. Access 2016 For Dummies Cheat Sheet

    4. MySQLフェッチ時間の最適化