Calculate sum for items in nested jsonb array - postgresql

So I have Postgres DB table 'purchases' with columns 'id' and 'receipt'. 'id' is primary int column, the value in column 'receipt' is jsonb and can look like this:
{
"shop_name":"some_shop_name",
"items": [
{
"name": "foo",
"spent": 49,
"quantity": 1
},
{
"name": "bar",
"price": 99,
"quantity": 2
},
{
"name": "abc",
"price": 999,
"quantity": 1
},
...
]
}
There can be varied amount of items in receipt.
In the end I need to write a query so the resulting table contains purchase id and amount spent on all bar for every purchase in table as such:
id
spent
1
198
..
...
My issue:
I can't figure out how to work with jsonb inside select query along regular columns in the resulting table, I guess query should be structured as this:
SELECT p.id, %jsonb_parsing_result_here% AS spent
FROM purchases p
It's blocking me from moving further with iterating through items in FOR cycle (or maybe using another way).

You can unnest the items from the receipt column with a lateral join and the jsonb_array_elements function like:
SELECT p.id, SUM((item->>'price')::NUMERIC)
FROM purchases p,
LATERAL jsonb_array_elements(p.receipt->'items') r (item)
GROUP BY p.id
It's also possible to add a where condition, for example:
WHERE item->>'name' = 'bar

Related

Update property of object in jsonb array and keep other properties

I have a postgres table with a jsonb colum like this:
create table if not exists doc
(
id uuid not null
constraint pkey_doc_id
primary key,
data jsonb not null
);
INSERT INTO doc (id, data) VALUES ('3cf40366-ea58-402d-b63b-c9d6fdf99ec8', '{"Id": "3cf40366-ea58-402d-b63b-c9d6fdf99ec8", "Tags": [{"Key": "inoivce", "Value": "70086"},{"Key": "customer", "Value": "100233"}] }' );
INSERT INTO doc (id, data) VALUES ('ae2d1119-adb9-41d2-96e9-53445eaf97ab', '{"Id": "ae2d1119-adb9-41d2-96e9-53445eaf97ab", "Tags": [{"Key": "project", "Value": "12345"},{"Key": "customer", "Value": "100233"}]}' );b9-41d2-96e9-53445eaf97ab", "Tags": [{"Key": "customer", "Value": "100233"}]}' )
Tags.Key in the first row contains a typo inoivce which I want to fix to invoice:
{
"Id": "3cf40366-ea58-402d-b63b-c9d6fdf99ec8",
"Tags": [{
"Key": "inoivce",
"Value": "70086"
},{
"Key": "customer",
"Value": "100233"
}]
}
I tried this:
update doc set data = jsonb_set(
data,
'{"Tags"}',
$${"Key":"invoice"}$$
) where data #> '{"Tags": [{ "Key":"inoivce"}]}';
The typo gets fixed but I'm loosing the other Tags elements in the array:
{
"Id": "3cf40366-ea58-402d-b63b-c9d6fdf99ec8",
"Tags": [{"Key": "invoice"}]
}
How can I fix the typo without removing the other elements of the Tags array?
Dbfiddle for repro.
One possible solution, not so obvious : we need a CTE because the idea here is to loop on the 'Tags' jsonb array elements using the jsonb_agg aggregate function to rebuild the array, but the SET clause of an UPDATE doesn't accept aggregate functions ...
WITH list AS
( SELECT d.id, (d.data - 'Tags') || jsonb_build_object('Tags', jsonb_agg(jsonb_set(e.content, '{Key}' :: text[], to_jsonb(replace(e.content->>'Key', 'inoivce', 'invoice'))) ORDER BY e.id)) AS data
FROM doc AS d
CROSS JOIN LATERAL jsonb_array_elements(d.data->'Tags') WITH ORDINALITY AS e(content, id)
WHERE d.data #? '$.Tags[*] ? (exists(# ? (#.Key == "inoivce")))'
GROUP BY d.id, d.data
)
UPDATE doc AS d
SET data = l.data
FROM list AS l
WHERE d.id = l.id
see the result in dbfiddle

JSONB Represent jsonb key value like array

I have field extra with type jsonb in my table product. And I need get uniq key with uniq values for each key from all products row. Example data in extra field
{"SIZE": "110/116", "COLOUR": "Vit", "GENDER": "female", "AGE_GROUP": "Kids", "ALTERNATIVE_IMAGE": "some_path"}
for now I use query like this
select DISTINCT e.key, array_agg(DISTINCT e.value) as fields
from products AS p
join jsonb_each_text(p.extras) e on true
GROUP BY e.key
Ad have respnse (small part with some keys in full response all keys are present) like this
[
{
"key": "AGE_GROUP",
"fields": "{Adult,children,Kids}"
},
{
"key": "GENDER",
"fields": "{female,male,man}"
}
]
how to change it to array for fields alias ?
like this
[
{
"AGE_GROUP": ["Adult","children","Kids"]
},
{
"GENDER": ["female","male","man"]
}
]
or maybe whould be great like this
[
"some_alias": [{"AGE_GROUP": "Adult", "AGE_GROUP": "children", "AGE_GROUP": "Kids"}],
"some_alias": [{"GENDER": "female", "GENDER": "male", "GENDER": "man"}]
]
This will get you the former form I think:
select jsonb_agg(jsonb_build_object(k,v)) from (
select DISTINCT e.key, jsonb_agg(DISTINCT e.value) as fields
from products AS p
join jsonb_each_text(p.extras) e on true
GROUP BY e.key
) b(k,v);
Best regards,
Bjarni

Query in JSONB array of Postgres database

Below JSON is one of the column of type JSONB in my table 'logic', I want to query to check how many rows are there with type: QUESTION (any entry within conditions).
{
"name": null,
"conditions": [
{
"type": "QUESTION",
"question": {
}
},
{
"type": "QUESTION",
"question": {
}
},
{
"type": "FIELD",
"question": {
}
}
],
"expression": "A"
}
If you want to check the number of times "type": "QUESTION" entry appears within conditions of the jsonb column throughout the table.
select count(*) FROM logic CROSS JOIN LATERAL
jsonb_array_elements(jsonb_col->'conditions')as j(typ)
WHERE j->>'type' = 'QUESTION'
If you want to check the number of times "type": "QUESTION" entry appears within conditions for each row.
select jsonb_col,count(*) FROM logic CROSS JOIN LATERAL
jsonb_array_elements(jsonb_col->'conditions')as j(typ)
WHERE j->>'type' = 'QUESTION'
group by jsonb_col
If you want to check how many rows have at least one entry within conditions with
'type' = 'QUESTION',
select count(*) FROM
(
select DISTINCT jsonb_col FROM logic CROSS JOIN LATERAL
jsonb_array_elements(jsonb_col->'conditions')as j(typ)
WHERE j->>'type' = 'QUESTION'
)s;
Use the query which you find is appropriate for you
Demo

How can I do less than, greater than in JSON array object Postgres fields?

I want to retrieve data by specific field operation it store array of object. i want to add new object in it.
CREATE TABLE justjson ( id INTEGER, doc JSONB);
INSERT INTO justjson VALUES ( 1, '[
{
"name": "abc",
"age": "22"
},
{
"name": "def",
"age": "23"
}
]');
retrieve data where age is greater then and equal to 23 how is possible
eg using jsonb_array_elements:
t=# with a as (select *,jsonb_array_elements(doc) k from justjson)
select k from a where (k->>'age')::int >= 23;
k
------------------------------
{"age": "23", "name": "def"}
(1 row)

Returning set of records

I have a table that stores records broken into fields, ie. if I have this record as follows
{
"name": "John Doe"
"gender": "male"
}
Then this record is stored in the table in 2 rows, as follows
[
{ "id": "1", "col": "name", "value": "John Doe" },
{ "id": "1", "col": "gender", "value": "male" }
]
If I want to write a postgresql function, that returns the record back to the original form (all attributes in a row form, instead of the one attribute one row form), how can I do it?
(the table design was done as an experiment for data warehousing purpose)
If I understand you right, you could write something like that
select a.value, b.value
from table1 a
inner join table1 b on a.id = b.id and a.orderNum < b.orderNum
To make it simple you could introduce field orderNum
p.s. I do not have Postgress installed, so you possibly would fix my query.