web-dev-qa-db-ja.com

PostgreSQL-特定のサブノードのツリー内のすべてのIDを取得します

顧客の非バイナリツリーがあり、指定されたノードのツリー内のすべてのIDを取得する必要があります。

テーブルは非常にシンプルで、親IDと子IDを持つ結合テーブルです。これは、私が自分のデータベースに格納したツリーの表現です。

Enter image description here

この例では、ノード17を検索すると、14-17が必要になります。 11を検索すると、1-6-5-4-8-11-12-7-2-10-3が必要になります。

順序は重要ではありません。子をノードに追加するときの循環を避けるためにIDだけが必要です。

このクエリを作成しました。祖先の部分は正常に機能し、すべての親ノードを取得しますが、子孫の場合は問題があります。ツリーの一部しか取得できません。たとえば、ノード11では4-10-6-11-7-8を取得しているため、ツリーのすべての右側の部分がありません。

WITH RECURSIVE
-- starting node(s)
starting (parent, child) AS
(
  SELECT t.parent, t.child
  FROM public.customerincustomer AS t
  WHERE t.child = :node or t.parent = :node
)
,
ancestors (parent, child) AS
(
  SELECT t.parent, t.child
  FROM public.customerincustomer AS t
  WHERE t.parent IN (SELECT parent FROM starting)
  UNION ALL
  SELECT t.parent, t.child
  FROM public.customerincustomer AS t JOIN ancestors AS a ON t.child = a.parent
),
descendants (parent, child) AS
(
  SELECT t.parent, t.child
  FROM public.customerincustomer AS t
  WHERE t.parent IN (SELECT parent FROM starting) or t.child in (select child from starting)
  UNION ALL
  SELECT t.parent, t.child
  FROM public.customerincustomer AS t JOIN ancestors AS a ON t.parent = a.child
)

table ancestors
union all
table descendants

更新

ツリーテーブルに含まれている多くの例も、フォームのルート(root_id、null)です。

私の場合、このレコードはありません。

たとえば、最小のツリー14-> 17を例にとると、テーブルには親、子が1つしかない

14 17

5
Luca Nitti

非常に原始的な実装:

基本的に、問題を2つのサブ問題に分割します。

  • まず、問題のノードのすべての祖先(ノード自体を含む)を見つけます。ノードに親がない場合、これはそれ自体です。
  • 次に、それらすべての祖先(自分自身を含む)の子孫を見つけます。 ancestors結果セットに複数のノードがある場合があり、ここで重複が発生する可能性があるため、UNIONUNION ALLではなく)を使用してそれらを削除します。
  • 入力ノードが子のないルートであっても、クエリは機能することに注意してください。
  • また、データセットがツリーのフォレストではなく任意の有向グラフ(ノードに複数の親を含めることができる)の場合にも機能します。

クエリ:

WITH RECURSIVE 
ancestors (parent) AS
(
  SELECT :node                      -- start with the given node
  UNION ALL
  SELECT t.parent                   -- and find all its ancestors
  FROM public.customerincustomer AS t JOIN ancestors AS a ON t.child = a.parent
),
descendants (customer) AS
(
  SELECT parent AS customer       -- now start with all the ancestors
  FROM ancestors
  UNION 
  SELECT t.child                  -- and find all their descendants
  FROM public.customerincustomer AS t JOIN descendants AS d ON t.parent = d.customer
)
SELECT customer
FROM descendants ;
5
ypercubeᵀᴹ

この関数は、node_idの親レベルを返します。

親行の行(id、null)がないため、「レベル」行があります。

CREATE FUNCTION get_parent(node_id int)
RETURNS integer AS
$$
  WITH RECURSIVE get_parent AS
  (
      SELECT 
          t1.id, 
          t1.parent_id, 
          t1.name, 
          0 AS level
      FROM 
          tree t1
      WHERE 
          t1.id = node_id
      UNION ALL
      SELECT 
          t2.id, 
          t2.parent_id, 
          t2.name,
          level+1
      FROM 
          tree t2 
      INNER JOIN 
          get_parent ON get_parent.parent_id = t2.id
  )
  SELECT
      id
  FROM
      get_parent
  ORDER BY
      level DESC
  LIMIT 1 ;
$$
LANGUAGE SQL;

get_parent(7);を選択します。

 | get_parent | 
 | ---------:| 
 | 6 | 

現在、次のクエリは、親ノードに基づいてツリー構造全体を返します。

WITH RECURSIVE childs AS
(
    SELECT 
        t1.id, 
        t1.parent_id, 
        t1.name
    FROM 
        tree t1
    WHERE 
        t1.id = get_parent(7)
    UNION ALL
    SELECT 
        t2.id, 
        t2.parent_id, 
        t2.name
    FROM 
        tree t2 
    INNER JOIN 
        childs ON childs.id = t2.parent_id
)
SELECT
    id,
    parent_id,
    name
 FROM
    childs;
 id | parent_id |名前
-:| --------:| :------ 
 6 | 1 | Node 6 
 4 | 6 | Node 4 
 8 | 6 | Node 8 
 11 | 6 | Node 11 
 7 | 11 | Node 7 
 10 | 7 | Node 10 

db <> fiddle ここ

4
McNets