snippetsqlMinor
Create JSON object from recursive tree structure
Viewed 0 times
fromcreaterecursivestructurejsonobjecttree
Problem
Having this simple many-to-many self-referential structure.
An item owns other items through the
db<>fiddle here
I am trying to retrieve a whole tree structure as JSON for a given item.
For example, to query the item with
Desired output for
After digging into the JSON capabilities Postgres offers, I managed such output by repeating a nested query. Simple but ugly, and limited to the amount of repeats. :/
I've found out about recursive queries. The examples found here and there are not that simple. It's hard to finding an entrypoint to understanding the technique and adapt it to my needs.
I hope the example here will be simple enough to find help from experienced users.
An item owns other items through the
joins table:CREATE TABLE items (
item_id serial PRIMARY KEY
, title text
);
CREATE TABLE joins (
id serial PRIMARY KEY
, item_id int
, child_id int
);
INSERT INTO items (item_id, title) VALUES
(1, 'PARENT')
, (2, 'LEVEL 2')
, (3, 'LEVEL 3.1')
, (4, 'LEVEL 4.1')
, (5, 'LEVEL 4.2')
, (6, 'LEVEL 3.2')
;
INSERT INTO joins (item_id, child_id) VALUES
(1, 2)
, (2, 3)
, (3, 4)
, (3, 5)
, (2, 6)
;db<>fiddle here
I am trying to retrieve a whole tree structure as JSON for a given item.
For example, to query the item with
item_id 1 (pseudo-code):SELECT i.*, fulltree from items i where item_id = 1;Desired output for
fulltree:{
id: 1,
title: "PARENT",
children: [
{
id: 2,
title: "LEVEL 2",
children: [
{
id: 3,
title: "LEVEL 3.1",
children: [
{
id: 4,
title: "LEVEL 4.1"
},
{
id: 5,
title: "LEVEL 4.2"
}
]
},
{
id: 6,
title: "LEVEL 3.2"
}
]
}
]
}After digging into the JSON capabilities Postgres offers, I managed such output by repeating a nested query. Simple but ugly, and limited to the amount of repeats. :/
I've found out about recursive queries. The examples found here and there are not that simple. It's hard to finding an entrypoint to understanding the technique and adapt it to my needs.
I hope the example here will be simple enough to find help from experienced users.
Solution
A recursive CTE (rCTE) does not allow aggregation in the recursive term. So there is no simple solution.
I suggest a recursive function for an elegant solution:
fiddle
Bare call:
To strip objects with NULL value (no children) and prettify:
Produces your desired output exactly (one complete tree):
Deletes all object fields that have null values from the given JSON value, recursively.
If there can be other fields with
fiddle
Later, closely related answer with alternatives (most notably a maximum recursion level):
I suggest a recursive function for an elegant solution:
CREATE OR REPLACE FUNCTION f_item_tree(_item_id int)
RETURNS jsonb
LANGUAGE sql STABLE PARALLEL SAFE AS
$func$
SELECT jsonb_agg(sub)
FROM (
SELECT i.*, f_item_tree(i.item_id) AS children
FROM joins j
JOIN items i ON i.item_id = j.child_id
WHERE j.item_id = _item_id
ORDER BY i.item_id
) sub
$func$;fiddle
Bare call:
SELECT to_jsonb(sub) AS tree
FROM (
SELECT *, f_item_tree(item_id) AS children
FROM items
WHERE item_id = 1 -- root item_id HERE
) sub;To strip objects with NULL value (no children) and prettify:
SELECT jsonb_pretty(jsonb_strip_nulls(to_jsonb(sub))) AS tree
FROM (
SELECT *, f_item_tree(item_id) AS children
FROM items
WHERE item_id = 1 -- root item_id HERE
) sub;Produces your desired output exactly (one complete tree):
{
"title": "PARENT",
"item_id": 1,
"children": [
{
"title": "LEVEL 2",
"item_id": 2,
"children": [
{
"title": "LEVEL 3.1",
"item_id": 3,
"children": [
{
"title": "LEVEL 4.1",
"item_id": 4
},
{
"title": "LEVEL 4.2",
"item_id": 5
}
]
},
{
"title": "LEVEL 3.2",
"item_id": 6
}
]
}
]
}
jsonb_strip_nulls() ...Deletes all object fields that have null values from the given JSON value, recursively.
If there can be other fields with
null values that you want to keep, you have to do more. Like:CREATE OR REPLACE FUNCTION f_item_tree(_item_id int)
RETURNS jsonb
LANGUAGE sql STABLE PARALLEL SAFE AS
$func$
SELECT jsonb_agg(
CASE WHEN children IS NULL
THEN to_jsonb(sub) - 'children'
-- THEN jsonb_build_object('title', title, 'item_id', item_id) -- alt: spell out
ELSE to_jsonb(sub) END
)
FROM (
SELECT i.*, f_item_tree(i.item_id) AS children
FROM joins j
JOIN items i ON i.item_id = j.child_id
WHERE j.item_id = _item_id
ORDER BY i.item_id
) sub
$func$;fiddle
Later, closely related answer with alternatives (most notably a maximum recursion level):
- How to turn a set of flat trees into a single tree with multiple leaves?
Code Snippets
CREATE OR REPLACE FUNCTION f_item_tree(_item_id int)
RETURNS jsonb
LANGUAGE sql STABLE PARALLEL SAFE AS
$func$
SELECT jsonb_agg(sub)
FROM (
SELECT i.*, f_item_tree(i.item_id) AS children
FROM joins j
JOIN items i ON i.item_id = j.child_id
WHERE j.item_id = _item_id
ORDER BY i.item_id
) sub
$func$;SELECT to_jsonb(sub) AS tree
FROM (
SELECT *, f_item_tree(item_id) AS children
FROM items
WHERE item_id = 1 -- root item_id HERE
) sub;SELECT jsonb_pretty(jsonb_strip_nulls(to_jsonb(sub))) AS tree
FROM (
SELECT *, f_item_tree(item_id) AS children
FROM items
WHERE item_id = 1 -- root item_id HERE
) sub;CREATE OR REPLACE FUNCTION f_item_tree(_item_id int)
RETURNS jsonb
LANGUAGE sql STABLE PARALLEL SAFE AS
$func$
SELECT jsonb_agg(
CASE WHEN children IS NULL
THEN to_jsonb(sub) - 'children'
-- THEN jsonb_build_object('title', title, 'item_id', item_id) -- alt: spell out
ELSE to_jsonb(sub) END
)
FROM (
SELECT i.*, f_item_tree(i.item_id) AS children
FROM joins j
JOIN items i ON i.item_id = j.child_id
WHERE j.item_id = _item_id
ORDER BY i.item_id
) sub
$func$;Context
StackExchange Database Administrators Q#195603, answer score: 3
Revisions (0)
No revisions yet.