PostgreSQL оставил объединение массива объектов запроса соединения - PullRequest
3 голосов
/ 07 октября 2019

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

table "person" with columns: person_id, person_name
table "pet" with columns: pet_id, owner_id, pet_name

person data:
1, 'John'
2, 'Jill'
3, 'Mary'

pet data:
1, 1, 'Fluffy'
2, 1, 'Buster'
3, 2, 'Doggy'

Как написать запрос на выборку из person левое соединение pet на person_id = owner_id с агрегатными функциями, поэтому мои данные результата выглядят так:

1,[{pet_id:1,pet_name:'Fluffy'},{pet_id:2,pet_name:'Buster'}],'John'
2,[{pet_id:3,pet_name:'Doggy'}],'Jill'
3,[],'Mary'

Ответы [ 4 ]

2 голосов
/ 07 октября 2019

demo: db <> fiddle

select
    COALESCE(
        json_agg(row_to_json(row(p2.pet_id::text, p2.pet_name))) FILTER (WHERE pet_id IS NOT NULL), 
       '[]'
    ) as json,
    p1.person_name
from person p1
left join pet p2
    on p1.person_id = p2.owner_id
group by
    p1.person_name;
  1. FILTER, чтобы отфильтровать NULL значения. Это создает значение NULL для Мэри.
  2. Если вы хотите добавить пустой массив JSON: используйте COALESCE, который заменит NULL значением по умолчанию
2 голосов
/ 07 октября 2019

Встроенные в JSON функции Postgres и функции агрегирования могут удовлетворить ваши требования:

select
    json_agg(row_to_json(row(p2.pet_id::text, p2.pet_name))) as json,
    p1.person_name
from person p1
left join pet p2
    on p1.person_id = p2.owner_id
group by
    p1.person_name;
1 голос
/ 07 октября 2019

Используйте LEFT JOIN LATERAL и агрегируйте в подзапросе:

SELECT p.person_id, COALESCE(pet.pets, '[]') AS pets, p.person_name
FROM   person p
LEFT   JOIN LATERAL (
   SELECT json_agg(json_build_object('pet_id', pet.pet_id
                                   , 'pet_name', pet.pet_name)) AS pets
   FROM   pet
   WHERE  pet.owner_id = p.person_id
   ) pet ON true
ORDER  BY p.person_id;  -- optional, Q suggests ordered results

db <> fiddle здесь

Таким образом, вам не нужно агрегировать результаты из внешнего запроса. Проще и чище, когда ваш внешний запрос более сложен, чем пример в вопросе. При объединении нескольких связанных таблиц это даже становится необходимостью:

Обычно также намного быстрее , когда во внешней таблице есть предикаты person - типичный вариант использования.

Убедитесь, что есть индекс на pet(owner_id), чтобы сделать это быстро. Или даже один на pet(owner_id, pet_id, pet_name) (или pet(owner_id) INCLUDE (pet_id, pet_name) в Postgres 11 или новее), если ваша строка не широкая, как в вашем примере, и вы получаете от нее только индексные сканы.

О,и используйте json_build_object() для сохранения атрибута names для произвольного выбора:

Похожие:

1 голос
/ 07 октября 2019
select
    person_id,
    jsonb_agg(to_jsonb(pet) - 'owner_id'),
    person_name
from person
left join pet on person_id = owner_id
group by person_id;

 person_id |                                 jsonb_agg                                  | person_name 
-----------+----------------------------------------------------------------------------+-------------
         1 | [{"pet_id": 1, "pet_name": "Fluffy"}, {"pet_id": 2, "pet_name": "Buster"}] | John
         2 | [{"pet_id": 3, "pet_name": "Doggy"}]                                       | Jill
         3 | [null]                                                                     | Mary
(3 rows)

Db <> скрипка.

...