I have the following table structure in my 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}
I am using GROUP BY query for this records, like below
SELECT SUM(PIECES) FROM detail_table GROUP BY item_id HAVING item_id =2
Using which I will get the total pieces as 30. But how could I get the count of total pieces from material_detail group by material_id.
I want result something like this
pieces | material_detail -------| ------------------ 30 | [{"material_id":1,"pieces":50},{"material_id":2,"pieces":20},{"material_id":3,"pieces":30}]
As I am from MySQL background, I don’t know how to achieve this with JSON fields in Postgres.
Note: material_detail column is of JSONB type.
Advertisement
Answer
You are aggregating on two different levels. I can’t think of a solution that wouldn’t need two separate aggregation steps. Additionally to aggregate the material information all arrays of the item_id have to be unnested first, before the actual pieces value can be aggregated for each material_id. Then this has to be aggregated back into a JSON array.
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 ;