У меня есть таблица в базе данных Postgres 11.4 с самодостаточной древовидной структурой:
+------------+
| account |
+------------+
| id |
| code |
| type |
| parentId | -- references account.id
+------------+
У каждого ребенка может быть еще один ребенок, уровень вложенности не ограничен.
Я хочу сгенерировать из него один объект JSON, вложив всех детей (ресурсивно).
Можно ли решить это одним запросом? Или любое другое решение, использующее typeORM с одной таблицей?
Иначе мне придется вручную привязать данные на стороне сервера.
Я пробовал этот запрос:
SELECT account.type, json_agg(account) as accounts
FROM account
-- LEFT JOIN "account" "child" ON "child"."parentId"="account"."id" -- tried to make one column child
GROUP BY account.type
Результат:
[
...
{
"type": "type03",
"accounts": [
{
"id": 28,
"code": "acc03.001",
"type": "type03",
"parentId": null
},
{
"id": 29,
"code": "acc03.001.001",
"type": "type03",
"parentId": 28
},
{
"id": 30,
"code": "acc03.001.002",
"type": "type03",
"parentId": 28
}
]
}
...
]
Вместо этого я ожидаю этого:
[
...
{
"type": "type03",
"accounts": [
{
"id": 28,
"code": "acc03.001",
"type": "type03",
"parentId": null,
"child": [
{
"id": 29,
"code": "acc03.001.001",
"type": "type03",
"parentId": 28
},
{
"id": 30,
"code": "acc03.001.002",
"type": "type03",
"parentId": 28
}
]
}
]
}
...
]
Может ли он иметь более высокий уровень вложенности (например, acc03.001.001.0001, родительский элемент будет acc03.001.001)? Может ли он иметь несколько корней в таблице (с parentId=null
) для каждого типа? — person DreaMy schedule 09.06.2020
Ваша версия Postgres (всегда)? Сколько уровней вложенности возможно? — person DreaMy schedule 09.06.2020
да, у ребенка может быть еще одно гнездо. код просто пример, может быть случайным. К сожалению, parentId имеет значение null и вложенный дочерний элемент. я уже редактировал. — person DreaMy schedule 09.06.2020
Так что это для более простой версии без многоуровневого вложения: «; с nested_accounts as (SELECT account.type, account.parentId, json_agg (account) as account from account group by type, parentId) select a.type, na. учетные записи из nested_accounts для внутреннего присоединения к учетной записи a.id = na.parentId; « Для мульти-вложенности, вероятно, должна быть добавлена рекуррентность. — person DreaMy schedule 09.06.2020
@ Адам: Да, верный путь. Но с некоторой рекуррентностью оказалось непросто. — person DreaMy schedule 09.06.2020
Это сложно. (Если я не упустил что-то важное, в чем я сомневаюсь.)
Это рекурсивная проблема, но стандартная рекурсивная CTE плохо справляются с этим, поскольку нам нужно агрегировать на всех уровнях, а CTE не допускают агрегирования в рекурсивном члене.
Я решил это с помощью функции PL / pgSQL:
Звоните (точно возвращает желаемый результат):
db ‹› fiddle здесь — с расширенным тестовым примером
Я выбрал имя ключа
children
вместоchild
, так как несколько могут быть вложенными.jsonb_pretty()
для улучшения отображения по желанию.Это предполагает ссылочную целостность; должен быть реализован с ограничением FK.
Решение может быть проще для вашего конкретного случая, если использовать столбец
code
— если он демонстрирует (нераскрытые) полезные свойства. Как мы могли бы получить уровень вложенности без rCTE и добавить временную таблицуt
. Но я стремлюсь к общему решению, основанному только на идентификаторах.В функции много чего происходит. Я добавил встроенные комментарии. По сути, он делает это:
lvl
)jsonb
с верхнего уровня вложенности вниз.Запишите все промежуточные результаты во вторую временную таблицу
j
.Функция принимает
_type
в качестве параметра, чтобы вернуть только данный тип. В противном случае обрабатывается вся таблица.В сторону: по возможности избегайте идентификаторов со смешанным регистром, таких как
"parentId"
в Postgres. Видеть:Связанный более поздний ответ с использованием рекурсивной функции:
Хороший. В какой-то момент я думал о временных таблицах, но остановился на этом единственном требовании запроса. — person DreaMy; 09.06.2020