Recursively generate JSON tree from hierarchical table in Postgres and jOOQ

Viewed 522

I have a hierarchical table in Postgres database, e.g. category. The structure is simple like this:

id parent_id name
1 null A
2 null B
3 1 A1
4 3 A1a
5 3 A1b
6 2 B1
7 2 B2

What i need to get from this table is recursive deep tree structure like this:

[
  {
    "id": 1,
    "name": "A",
    "children": [
      {
        "id": 3,
        "name": "A1",
        "children": [
          {
            "id": 4,
            "name": "A1a",
            "children": []
          },
          {
            "id": 5,
            "name": "A1b",
            "children": []
          }
        ]
      }
    ]
  },
  {
    "id": 2,
    "name": "B",
    "children": [
      {
        "id": 6,
        "name": "B1",
        "children": []
      },
      {
        "id": 7,
        "name": "B2",
        "children": []
      }
    ]
  },
]

Is it possible with unknown depth using combination of WITH RECURSIVE and json_build_array() or some other solution?

1 Answers

I found an answer to this question in this excellent blog post here, as I was wondering how to generalise over this problem in jOOQ. It would be useful if jOOQ could materialise arbitrary recursive object trees in a generic way: https://github.com/jOOQ/jOOQ/issues/12341

In the meantime, use this SQL statement, which was inspired by the above blog post, with a few modifications. Translate to jOOQ if you must, though you might as well store this as a view:

WITH RECURSIVE
  d1 (id, parent_id, name) as (
    values
      (1, null, 'A'),
      (2, null, 'B'),
      (3,    1, 'A1'),
      (4,    3, 'A1a'),
      (5,    3, 'A1b'),
      (6,    2, 'B1'),
      (7,    2, 'B2')
  ),
  d2 AS (
    SELECT d1.*, 0 AS level
    FROM d1
    WHERE parent_id IS NULL
    UNION ALL
    SELECT d1.*, d2.level + 1
    FROM d1
    JOIN d2 ON d2.id = d1.parent_id
  ),
  d3 AS (
    SELECT d2.*, jsonb_build_array() children
    FROM d2
    WHERE level = (SELECT max(level) FROM d2)
    UNION (
      SELECT (branch_parent).*, jsonb_agg(branch_child)
      FROM (
        SELECT 
          branch_parent, 
          to_jsonb(branch_child) - 'level' - 'parent_id' AS branch_child
        FROM d2 branch_parent
        JOIN d3 branch_child ON branch_child.parent_id = branch_parent.id
      ) branch
      GROUP BY branch.branch_parent
      UNION
      SELECT d2.*, jsonb_build_array()
      FROM d2
      WHERE d2.id NOT IN (
        SELECT parent_id FROM d2 WHERE parent_id IS NOT NULL
      )
    )
  )
SELECT jsonb_pretty(jsonb_agg(to_jsonb(d3) - 'level' - 'parent_id')) AS tree
FROM d3
WHERE level = 0;

dbfiddle. Again, read the linked blog post for an explanation of how this works

Related