Надеясь, что это ваш ожидаемый результат.(Я сделал что-то подобное здесь: https://stackoverflow.com/a/52076212/3984221)
демо: db <> fiddle
Таблица comments
:
id body user_id likes
-- ------------ ------- -----
a foo 1 1
b foofoo 1 232
c foofoofoo 1 23232
d fooFOO 1 53
e cookies 1 864
f bar 1 44
g barbar 1 54
h barBAR 1 222
i more cookies 1 1
Таблица replies
id parent_id
-- ---------
a (null)
b a
c b
d a
e (null)
f (null)
g f
h f
i (null)
Результат :
{
"comments": [{
"children": [],
"username": "Mike Tyson",
"comment_id": "i",
"comment_body": "more cookies",
"comment_likes": 1
},
{
"children": [{
"children": [],
"username": "Mike Tyson",
"comment_id": "b",
"comment_body": "foofoo",
"comment_likes": 232
},
{
"children": [{
"children": [],
"username": "Mike Tyson",
"comment_id": "c",
"comment_body": "foofoofoo",
"comment_likes": 23232
}],
"username": "Mike Tyson",
"comment_id": "d",
"comment_body": "fooFOO",
"comment_likes": 53
}],
"username": "Mike Tyson",
"comment_id": "a",
"comment_body": "foo",
"comment_likes": 1
},
{
"children": [],
"username": "Mike Tyson",
"comment_id": "e",
"comment_body": "cookies",
"comment_likes": 864
},
{
"children": [{
"children": [],
"username": "Mike Tyson",
"comment_id": "g",
"comment_body": "barbar",
"comment_likes": 54
},
{
"children": [],
"username": "Mike Tyson",
"comment_id": "h",
"comment_body": "barBAR",
"comment_likes": 222
}],
"username": "Mike Tyson",
"comment_id": "f",
"comment_body": "bar",
"comment_likes": 44
}]
}
Запрос :
Рекурсия :
WITH RECURSIVE parent_tree AS (
SELECT
id,
NULL::text[] as parent_id,
array_append('{comments}'::text[], (row_number() OVER ())::text) as path,
rc.children
FROM replies r
LEFT JOIN LATERAL (SELECT parent_id, ARRAY_AGG(id) as children FROM replies WHERE parent_id = r.id GROUP BY parent_id) rc ON rc.parent_id = r.id
WHERE r.parent_id IS NULL
UNION
SELECT
r.id,
array_append(pt.parent_id, r.parent_id),
array_append(array_append(pt.path, 'children'), (row_number() OVER (PARTITION BY pt.parent_id))::text),
rc.children
FROM parent_tree pt
JOIN replies r ON r.id = ANY(pt.children)
LEFT JOIN LATERAL (SELECT parent_id, ARRAY_AGG(id) as children FROM replies WHERE parent_id = r.id GROUP BY parent_id) rc ON rc.parent_id = r.id
), json_objects AS (
SELECT c.id, jsonb_build_object('children', '[]'::jsonb, 'comment_id', c.id, 'username', u.name, 'comment_body', c.body, 'comment_likes', c.likes) as jsondata
FROM comments c
LEFT JOIN users u ON u.id = c.user_id
)
SELECT
parent_id,
path,
jsondata
FROM parent_tree pt
LEFT JOIN json_objects jo ON pt.id = jo.id
ORDER BY parent_id NULLS FIRST
Единственная часть рекурсии находится в CTE parent_tree
. Здесь я ищу родителей и строю путь.Этот путь необходим для вставки данных json позже в правильную позицию.
Второй CTE (json_objects
) создает объект json для каждого комментария с пустым массивом потомков, куда потом могут быть вставлены потомки.
Объединение LATERAL
ищет в таблице ответов дочерние элементы текущего идентификатора и выдает массив с их идентификаторами.
Предложение ORDER BY
в конце имеет важное значение.что все верхние узлы предшествуют нижним узлам (их дочерним элементам). В противном случае вход в глобальный объект json может завершиться ошибкой позже, поскольку не может существовать необходимый родительский элемент.в нужный момент.
Создание конечного объекта JSON :
CREATE OR REPLACE FUNCTION json_tree() RETURNS jsonb AS $$
DECLARE
_json_output jsonb;
_temprow record;
BEGIN
SELECT
jsonb_build_object('comments', '[]'::jsonb)
INTO _json_output;
FOR _temprow IN
-- <query above>
LOOP
SELECT jsonb_insert(_json_output, _temprow.path, _temprow.jsondata) INTO _json_output;
END LOOP;
RETURN _json_output;
END;
$$ LANGUAGE plpgsql;
Невозможно построить объект json в рамках рекурсиипотому что в запросе объект jsondata
не является глобальной переменной.Поэтому, если бы я добавил b
как дочерний элемент в a
в одной ветви рекурсии, он не существовал бы в другой ветви, где я бы добавил c
как дочерний элемент.
Поэтому необходимо сгенерироватьглобальная переменная.Это может быть сделано в функции.С вычисленным путем и дочерними объектами действительно просто построить окончательный JSON вместе: перебрать набор результатов и добавить объект JSON в путь глобального объекта.