6

次のようなpostgres 9.3.5のテーブルがあります。

CREATE TABLE customer_area_node
(
  id bigserial NOT NULL,
  customer_id integer NOT NULL,
  parent_id bigint,
  name text,
  description text,

  CONSTRAINT customer_area_node_pkey PRIMARY KEY (id)
)

私は次のようにクエリします:

WITH RECURSIVE c AS (
       SELECT *, 0 as level, name as path FROM customer_area_node WHERE customer_id = 2 and parent_id is null
       UNION ALL
       SELECT customer_area_node.*, 
       c.level + 1 as level, 
       c.path || '/' || customer_area_node.name as path
  FROM customer_area_node 
  join c ON customer_area_node.parent_id = c.id
)
SELECT * FROM c ORDER BY path;

これは、 building1/floor1/room1 、 building1/floor1/room2 などのパスを構築するために機能するようです.

私ができるようにしたいのは、それを、row_to_jsonでできると言われているツリー構造を表すjsonに簡単に変換することです。

合理的な代替手段として、データをより効率的なメカニズムにフォーマットして、/ に多数の string.splits を持たなくても実際に簡単に実際のツリー構造に変換できる他の方法があります。

row_to_json でこれを行う合理的に簡単な方法はありますか?

4

3 に答える 3

10

非常に遅い回答で申し訳ありませんが、この質問に対して受け入れられる回答になる可能性のあるエレガントなソリューションを見つけたと思います。

@pozs が見つけたすばらしい「小さなハック」に基づいて、次のような解決策を思いつきました。

  • 非常に少ないコードで「不正な葉」の状況を解決します (NOT EXISTS述語を活用)
  • レベル計算/条件全体を回避します
WITH RECURSIVE customer_area_tree("id", "customer_id", "parent_id", "name", "description", "children") AS (
  -- tree leaves (no matching children)
  SELECT c.*, json '[]'
  FROM customer_area_node c
  WHERE NOT EXISTS(SELECT * FROM customer_area_node AS hypothetic_child WHERE hypothetic_child.parent_id = c.id)

  UNION ALL

  -- pozs's awesome "little hack"
  SELECT (parent).*, json_agg(child) AS "children"
  FROM (
    SELECT parent, child
    FROM customer_area_tree AS child
    JOIN customer_area_node parent ON parent.id = child.parent_id
  ) branch
  GROUP BY branch.parent
)
SELECT json_agg(t)
FROM customer_area_tree t
LEFT JOIN customer_area_node AS hypothetic_parent ON(hypothetic_parent.id = t.parent_id)
WHERE hypothetic_parent.id IS NULL

更新

非常に単純なデータでテストされ、機能しますが、posz がコメントで指摘したように、彼のサンプル データでは、いくつかの不正なリーフ ノードが忘れられています。しかし、さらに複雑なデータでは、前の回答も機能しないことがわかりました。これは、「最大レベル」のリーフ ノードと共通の祖先を持つ不正なリーフ ノードのみが捕捉されるためです (「1.2.5.8」が存在しない場合、「 1.2.4" と "1.2.5" は、「最大レベル」のリーフ ノードと共通の祖先がないため、存在しません)。

NOT EXISTSそこで、サブリクエストを抽出して internalUNIONにすることで posz の作業を私のものと混ぜ合わせ、UNION重複排除機能を活用する (jsonb 比較機能を活用する)新しい提案を次に示します。

<!-- language: sql -->
WITH RECURSIVE
c_with_level AS (

    SELECT *, 0 as lvl
    FROM   customer_area_node
    WHERE  parent_id IS NULL

    UNION ALL

    SELECT child.*, parent.lvl + 1
    FROM   customer_area_node child
    JOIN   c_with_level parent ON parent.id = child.parent_id
),
maxlvl AS (
  SELECT max(lvl) maxlvl FROM c_with_level
),
c_tree AS (
    SELECT c_with_level.*, jsonb '[]' children
    FROM   c_with_level, maxlvl
    WHERE  lvl = maxlvl

    UNION 
    (
        SELECT (branch_parent).*, jsonb_agg(branch_child)
        FROM (
            SELECT branch_parent, branch_child
            FROM c_with_level branch_parent
            JOIN c_tree branch_child ON branch_child.parent_id = branch_parent.id
        ) branch
        GROUP BY branch.branch_parent

        UNION

        SELECT c.*, jsonb '[]' children
        FROM   c_with_level c
        WHERE  NOT EXISTS (SELECT 1 FROM c_with_level hypothetical_child WHERE hypothetical_child.parent_id = c.id)
    )
)
SELECT jsonb_pretty(row_to_json(c_tree)::jsonb)
FROM c_tree
WHERE lvl = 0;

http://rextester.com/SMM38494でテスト済み;)

于 2017-05-11T13:36:04.810 に答える
8

通常の再帰 CTE ではこれを行うことはできません。階層の奥深くに json 値を設定することはほとんど不可能だからです。しかし、それを逆にすることもできます: 葉から始まり、根までツリーを構築します:

-- calculate node levels
WITH RECURSIVE c AS (
    SELECT *, 0 as lvl
    FROM customer_area_node
    -- use parameters here, to select the root first
    WHERE customer_id = 2 AND parent_id IS NULL
  UNION ALL
    SELECT customer_area_node.*, c.lvl + 1 as lvl
    FROM customer_area_node 
    JOIN c ON customer_area_node.parent_id = c.id
),
-- select max level
maxlvl AS (
  SELECT max(lvl) maxlvl FROM c
),
-- accumulate children
j AS (
    SELECT c.*, json '[]' children -- at max level, there are only leaves
    FROM c, maxlvl
    WHERE lvl = maxlvl
  UNION ALL
    -- a little hack, because PostgreSQL doesn't like aggregated recursive terms
    SELECT (c).*, array_to_json(array_agg(j)) children
    FROM (
      SELECT c, j
      FROM j
      JOIN c ON j.parent_id = c.id
    ) v
    GROUP BY v.c
)
-- select only root
SELECT row_to_json(j) json_tree
FROM j
WHERE lvl = 0;

そして、これはPostgreSQL 9.2+でも機能します

SQLフィドル

更新:不正なリーフノードも処理する必要があるバリアント (レベルが 1 からmax-level にある):

WITH RECURSIVE c AS (
    SELECT *, 0 as lvl
    FROM   customer_area_node
    WHERE  customer_id = 1 AND parent_id IS NULL
  UNION ALL
    SELECT customer_area_node.*, c.lvl + 1
    FROM   customer_area_node 
    JOIN   c ON customer_area_node.parent_id = c.id
),
maxlvl AS (
  SELECT max(lvl) maxlvl FROM c
),
j AS (
    SELECT c.*, json '[]' children
    FROM   c, maxlvl
    WHERE  lvl = maxlvl
  UNION ALL
    SELECT   (c).*, array_to_json(array_agg(j) || array(SELECT r
                                                        FROM   (SELECT l.*, json '[]' children
                                                                FROM   c l, maxlvl
                                                                WHERE  l.parent_id = (c).id
                                                                AND    l.lvl < maxlvl
                                                                AND    NOT EXISTS (SELECT 1
                                                                                   FROM   c lp
                                                                                   WHERE  lp.parent_id = l.id)) r)) children
    FROM     (SELECT c, j
              FROM   c
              JOIN   j ON j.parent_id = c.id) v
    GROUP BY v.c
)
SELECT row_to_json(j) json_tree
FROM   j
WHERE  lvl = 0;

これは PostgreSQL 9.2 以降でも動作するはずですが、テストできません。(現在、9.5+ でしかテストできません)。

これらのソリューションは、任意の階層テーブル内の任意の列を処理できますが、常にint型指定されたlvlJSON プロパティを出力に追加します。

http://rextester.com/YNU7932

于 2014-09-05T09:52:24.473 に答える
0

サブツリーで再帰的な葉を取得するために、pozs の答えをもう少し発展させました。したがって、この答えは実際には完全なツリーを返します。

CREATE OR REPLACE FUNCTION pg_temp.getTree(bigint) 
    RETURNS TABLE( 
            id bigint,
            customer_id integer,
            parent_id bigint,
            name text,
            description text,
            children json
        ) 
        AS $$   

        WITH RECURSIVE relations AS ( 
            SELECT 
                can.id,
                can.customer_id,
                can.parent_id,
                can.name, 
                can.description,
                0 AS depth 
                FROM customer_area_node can 
                WHERE can.id = $1 
            UNION ALL 
            SELECT 
                can.id,
                can.customer_id,
                can.parent_id,
                can.name, 
                can.description,
                relations.depth + 1 
                FROM customer_area_node can
                JOIN relations ON can.parent_id = relations.id AND can.id != can.parent_id
        ),     

        maxdepth AS ( 
            SELECT max(depth) maxdepth FROM relations 
        ), 

        rootTree as ( 
            SELECT r.* FROM 
                relations r, maxdepth 
                WHERE depth = maxdepth 
            UNION ALL 
            SELECT r.* FROM 
                relations r, rootTree 
                WHERE r.id = rootTree.parent_id AND rootTree.id != rootTree.parent_id 
        ), 

        mainTree AS ( 
            SELECT 
                c.id,
                c.customer_id,
                c.parent_id,
                c.name, 
                c.description,
                c.depth, 
                json_build_array() children 
                FROM relations c, maxdepth 
                WHERE c.depth = maxdepth 
            UNION ALL 
            SELECT 
                (relations).*, 
                array_to_json( 
                    array_agg(mainTree) 
                    || 
                    array( 
                        SELECT t 
                            FROM ( 
                                SELECT 
                                    l.*, 
                                    json_build_array() children 
                                FROM relations l, maxdepth 
                                    WHERE l.parent_id = (relations).id 
                                    AND l.depth < maxdepth 
                                    AND l.id  NOT IN ( 
                                        SELECT id FROM rootTree 
                                    ) 
                            ) r 
                           JOIN pg_temp.getTree(r.id) t 
                            ON r.id = t.id 
                        )) 
                children 
    FROM ( 
        SELECT relations, mainTree 
            FROM relations 
        JOIN mainTree 
            ON ( 
                mainTree.parent_id = relations.id 
                AND mainTree.parent_id != mainTree.id 
            ) 
    ) v 
    GROUP BY v.relations 
    ) 

        SELECT 
            id,
            customer_id,
            parent_id,
            name, 
            description,
            children 
        FROM mainTree WHERE id = $1 
    $$ 
    LANGUAGE SQL; 

    SELECT * 
    FROM 
        customer_area_node can 
        JOIN pg_temp.getTree(can.id) t ON t.id = can.id 
    WHERE can.parent_id IS NULL;
于 2017-06-29T09:39:57.260 に答える