Postgres recursive query with row_to_json

前端 未结 3 1414
温柔的废话
温柔的废话 2020-12-14 04:47

I\'ve got a table in postgres 9.3.5 that looks like this:

CREATE TABLE customer_area_node
(
  id bigserial NOT NULL,
  customer_id integer NOT NULL,
  parent         


        
3条回答
  •  轮回少年
    2020-12-14 05:34

    You cannot do that with a usual recursive CTE, because it is almost impossible to set a json value deep in its hierarchy. But you can do it reversed: build up the tree starting from its leaves, until its root:

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

    And this will work even with PostgreSQL 9.2+

    SQLFiddle

    Update: A variant, which should handle rogue leaf nodes too (which are located with a level between 1 and 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;
    

    This should work too on PostgreSQL 9.2+, however, I cannot test that. (I can only test on 9.5+ right now).

    These solutions can handle any column in any hierarchical table, but will always append an int typed lvl JSON property to their output.

    http://rextester.com/YNU7932

提交回复
热议问题