У меня есть простая таблица базы данных, которая реализует древовидную структуру через parent_id приписывать. Что-то вроде этого:
+----+------+-----------+
| id | name | parent_id |
+----+------+-----------+
| 1 | test | null |
+----+------+-----------+
| 2 | tes2 | 1 |
+----+------+-----------+
| 3 | tes3 | 2 |
+----+------+-----------+
| 4 | tst | 2 |
+----+------+-----------+
Я хочу получить объект PHP с древовидной структурой. Таким образом, объект Category будет иметь подкатегории свойств, которые будут списком объектов Category и так далее. Я хочу получить этот объект непосредственно из базы данных PostgreSQL через рекурсивный SQL-запрос с Pomm. Цель не в том, чтобы перебрать полученные данные и построить такой объект в PHP. Я хочу прямой процесс PostreSQL -> Pomm -> Object.
А пока я получаю то, что хочу, только на первом уровне. Таким образом, категория первого уровня имеет подкатегории, которые являются списком объектов категории. Однако следующий уровень (глубина 2) не имеет подкатегорий.
Пока у меня есть это:
$sql = <<<SQL
with recursive
cat as (select c.* FROM :category c where parent_id is null),
subcat as (
select c.* from :category c join cat on c.parent_id=cat.id
union all
select c.* from :category c join subcat on c.parent_id=subcat.id
)
select :projection from cat cc, subcat
where cc.id=subcat.parent_id
group by :group_fields
SQL;
$projection = $this->createProjection()
->setField('subcategories', 'array_agg(subcat)', 'public.category[]');
$sql = strtr($sql, [
':category' => $this->structure->getRelation(),
':projection' => $projection->formatFieldsWithFieldAlias('cc'),
':group_fields' => $this->createProjection()->formatFields('cc'),
]);
Мой вопрос, если это возможно с Pomm и если да, то как?
То, чего вы хотите достичь, на самом деле не возможно напрямую, потому что в Pomm по причинам производительности, когда выполняется запрос, итератор помещает курсор базы данных в результаты.
$iterator = $this->query($sql, $array_of_parameters);
foreach ($iterator as $entity) {
$entity->getParentId();
}
Каждый раз, когда данные выбираются из итератора, система преобразования превращает их в объект. Но сущности не знают о базе данных, поэтому они не могут получить больше данных, используя свои средства доступа.
Тривиальная идея состоит в том, чтобы получить один результат, содержащий все результаты как вложенные объекты:
with recursive
cat as (
select * from test_tree tt where not exists (select parent_id from test_tree tt2 where tt2.parent_id = tt.id)
union all
select tt.*, array_agg(child) from test_tree tt join cat child on tt.id = child.parent_id group by tt.id
)
select * from cat
Но, к сожалению, невозможно использовать агрегатные функции в рекурсивном члене CTE.
Другая идея заключалась бы в том, чтобы индексировать результаты по идентификатору, дающему потомков для каждого parent_id, и использовать эти итераторы Pomm с возможностью прокрутки для их получения:
with
tree as (
select
tt.id,
array_agg(child) as children
from
test_tree tt
join lateral (select * from test_tree tt2 where tt2.parent_id = tt.id) child on (true) group by tt.id
)
select
idx as id,
tree.children
from
generate_series(1, (select max(id) from test_tree)) idx
left join tree on tree.id = idx
какие выводы:
┌────┬─────────────────────────────────────────┐
│ id │ children │
├────┼─────────────────────────────────────────┤
│ 1 │ {"(2,\"test 2\",1)","(3,\"test 3\",1)"} │
│ 2 │ {"(4,\"test 4\",2)","(5,\"test 5\",2)"} │
│ 3 │ {"(6,\"test 6\",3)"} │
│ 4 │ ¤ │
│ 5 │ ¤ │
│ 6 │ {"(7,\"test 7\",6)"} │
│ 7 │ ¤ │
└────┴─────────────────────────────────────────┘
(7 rows)
Тогда набор результатов будет упорядочен по parent_id так $iterator->get($parent_id)
вернул бы массив дочерних объектов (или ноль), но это больше похоже на хак, чем на реальную функцию.
Если рассматривать проблему с другой стороны, представляется возможным создать выделенные гибкие объекты, которые встроить шаблон вложенного набора внутренне набирать на детей.
Других решений пока нет …