Как агрегировать поля json при использовании предложения GROUP BY в postgres? - PullRequest
0 голосов
/ 28 мая 2020

У меня есть следующая структура таблицы в моей Postgres DB (v12.0)

id | pieces | item_id | material_detail
---|--------|---------|-----------------
1  | 10     | 2       | [{"material_id":1,"pieces":10},{"material_id":2,"pieces":20},{"material_id":3,"pieces":30}]

2  | 20     | 2       | [{"material_id":1,"pieces":40}
3  | 30     | 3       | [{"material_id":1,"pieces":20},{"material_id":3,"pieces":30}

Я использую запрос GROUP BY для этих записей, как показано ниже

SELECT SUM(PIECES) FROM detail_table GROUP BY item_id HAVING item_id =2 

Используя это, я получу общее количество штук как 30. Но как я могу получить общее количество штук из группы material_detail по material_id.

Я хочу получить что-то вроде этого

 pieces |  material_detail
 -------| ------------------
  30    |  [{"material_id":1,"pieces":50},{"material_id":2,"pieces":20},{"material_id":3,"pieces":30}]

Поскольку я из MySQL фона, я не знаю, как добиться этого с полями JSON в Postgres.

Примечание: столбец material_detail имеет тип JSONB.

1 Ответ

3 голосов
/ 28 мая 2020

Вы агрегируете на двух разных уровнях. Я не могу придумать решение, для которого не потребовались бы два отдельных этапа агрегации. Кроме того, чтобы агрегировать информацию о материалах, все массивы item_id должны быть сначала не вложены, прежде чем фактическое значение штук может быть агрегировано для каждого material_id. Затем это должно быть снова объединено в массив JSON.

with pieces as (

  -- the basic aggregation for the "detail pieces"
  select dt.item_id, sum(dt.pieces) as pieces
  from detail_table dt
  where dt.item_id = 2
  group by dt.item_id

), details as (

  -- normalize the material information and aggregate the pieces per material_id
  select dt.item_id, (m.detail -> 'material_id')::int as material_id, sum((m.detail -> 'pieces')::int) as pieces
  from detail_table dt
    cross join lateral jsonb_array_elements(dt.material_detail) as m(detail)
  where dt.item_id in (select item_id from pieces) --<< don't aggregate too much    
  group by dt.item_id, material_id

), material as (

  -- now de-normalize the material aggregation back into a single JSON array
  -- for each item_id
  select item_id, jsonb_agg(to_jsonb(d) - 'item_id') as material_detail
  from details d
  group by item_id

)
-- join both results together
select p.item_id, p.pieces, m.material_detail
from pieces p
  join material m on m.item_id = p.item_id
; 

Онлайн-пример

...