Возврат агрегированного массива путем поиска / сравнения в массиве массивов

#sql #postgresql #postgresql-10

#sql #postgresql #postgresql-10

Вопрос:

У меня есть 2 таблицы:

Категории

 id | name |  | slug   | path | parent_id  | depth
1    name1     slug1    {1}      null       0
2    name2     slug2    {1,2}      1        1
3    name3     slug3    {1,2,3}    2        2
5    nam5      slug5    {5}       null      0
......
9    nam4      slug9    {5,9}       5       1
  

где path является int[]array типом и работает как хлебная крошка

Товары

    id | name
   1    name1 
  

Между элементом и категорией существует отношение M2M

item_categories

  id | item_id  | category_id 
   1        1    |  3
   2        1       9   
  

В приведенном выше примере элемент находится в 3 категориях:

Я использую следующий SQL:

 SELECT c1.id, c1.name, c1.slug, c1.parent_id FROM categories AS c1 
WHERE ARRAY[c1.id] <@ (SELECT c2.path FROM categories AS c2 WHERE c2.id= 
(SELECT category_id FROM item_categories WHERE item_id=8 LIMIT 1)) order by depth
  

чтобы извлечь базу данных по пути, и это работает.

Но я хочу получить все хлебные крошки (а не только один). Удаление LIMIT 1 и изменение = to in У меня будет массив массивов, а не просто массив, и вызовет ошибку:

более одной строки, возвращаемой подзапросом, используемым в качестве выражения

что нормально.

Пример того, что я хочу — Элемент в:

 cat1 - > cat2 - >cat3
ca5 -> cat9
  

и из базы данных (чтобы я мог перебирать их):

 [ [{'name':cat1, 'slug':slug1}, {'name':cat2, 'slug':slug2}, {'name':cat3, 'slug':slug3}], [{'name':cat5, 'slug':slug5}, {'name':cat9, 'slug':slug9}]]
  

dbfiddle: https://dbfiddle.uk/?rdbms=postgres_10amp;fiddle=f756cfe568d38425dfe25cfec60b1b3f

Итак, вместо получения одной хлебной крошки, как я могу получить массив из хлебных крошек в качестве результата?

Комментарии:

1. (1) Пожалуйста, покажите результаты, которые вы хотите. (2) Можете ли вы настроить db<>скрипку или что-то в этом роде?

2. 1) Я обновляю вопрос с помощью примера

3. ваш оператор sql ссылается на вызываемую таблицу categories_category , которая больше нигде не упоминается, это ошибка? Минимальный образец данных был бы полезен, вот начало , вы можете заполнить это?

4. @JackDouglas спасибо, это была ошибка, которую я обновил в dbfiddle; в моем заказе запроса по глубине у меня есть путь от верхней категории до последнего дочернего элемента. Проблема возникает, когда мне нужно передать несколько путей; Поэтому я подумал использовать массив array с элементами json для их группировки;

5. Если у вас есть обновленный db<>fiddle, вам необходимо поделиться ссылкой с нами, каждая итерация скрипки имеет уникальную ссылку.

Ответ №1:

используя json_build_object unnest и упорядочивая json_agg :

 select
     c.id,
     json_agg(
         json_build_object('name',c2.name,'slug',c2.slug)
         order by p.depth
     )
from categories as c
    inner join lateral unnest(c.path) with ordinality as p(id, depth) on true
    inner join categories as c2 on
        c2.id = p.id
where
    exists (
        select *
        from item_categories as tt
        where
            tt.item_id = 1 and
            tt.category_id = c.id
    )
group by
    c.id
  

db<>fiddle demo

Или вы можете использовать depth столбец из вашей таблицы, если хотите:

 select
     c.id,
     json_agg(
         json_build_object('name',c2.name,'slug',c2.slug)
         order by c2.depth
     )
from categories as c
    inner join categories as c2 on
        c2.id = any(c.path)
where
    exists (
        select *
        from item_categories as tt
        where
            tt.item_id = 1 and
            tt.category_id = c.id
    )
group by
    c.id
  

db<>fiddle demo

Что мне не нравится, так это то json_build_object , что вы должны называть свои столбцы явно, выполняя двойную работу, поэтому я попытался использовать to_json вместо этого. Это работает, но, честно говоря, я не очень знаком с этим синтаксисом, когда псевдоним таблицы передается функции в качестве аргумента (см. Using Composite Types in Queries ) и не может заставить его работать без lateral join:

 select
     c.id,
     json_agg(to_json(d) order by c2.depth)
from categories as c
    inner join categories as c2 on
        c2.id = any(c.path)
    cross join lateral (select c.name, c.slug) as d
where
    exists (
        select *
        from item_categories as tt
        where
            tt.item_id = 1 and
            tt.category_id = c.id
    )
group by
    c.id
  

db<>fiddle demo