How to correctly index jsonb arrays and the fields in array elements in postgreSQL - postgresql

a have a simple table of purchases consisting of id and jsonb column, like this:
CREATE TABLE purchase (id SERIAL, products JSONB)
Then and index:
CREATE INDEX idx_purchase_products ON purchase USING GIN (products);
The sample data are like this:
INSERT INTO purchase VALUES (
1, jsonb('[
{
"country": 1,
"type": 1,
"size": 10,
"color": 3
}
]')
),
(
2, jsonb('[
{
"country": 1,
"type": 1,
"size": 10,
"color": 3
},
{
"country": 1,
"type": 2,
"size": 12,
"color": 4
},
{
"country": 2,
"type": 1,
"size": 12,
"color": 3
}
]')
),
(
3, jsonb('[
{
"country": 1,
"type": 1,
"size": 10,
"color": 3
}
]')
),
(
4, jsonb('[
{
"country": 1,
"type": 1,
"size": 10,
"color": 3
},
{
"country": 1,
"type": 2,
"size": 12,
"color": 4
},
{
"country": 2,
"type": 1,
"size": 12,
"color": 3
}
]')
);
And some scenarios of searching:
SELECT *
FROM purchase
WHERE products #> '[{"country": 1}]'
SELECT *
FROM purchase
WHERE products #> '[{"country": 1, "type": 1}]'
SELECT *
FROM purchase
WHERE products #> '[{"size": 12}]'
SELECT *
FROM purchase
WHERE products #> '[{"size": 12, "color": 4}]'
It is expected, that the customer could search for combinations:
country,
country + type
country + type + size
country + type + size + color
country + size
size + color
type + color
etc.
And there is a big chance, the list of 4 field (country, type, size, color) will grow in future to 7-10.
And of course we want also search combinations like this:
.. WHERE products #> '[{"country": 1}]' OR products #> '[{"color": 4}]' OR products #> '[{"type": 1, "size": 10}]'
Estimated size of the table purchase is 9-12 millions rows (depending on season).
Any idea how to implement the indexes to get the query result as fast as possible?

Related

Jsonb array of objects update

So this is my jsonb array of objects. Column is called bids in my db.
bids column
[
{
"id": "1",
"size": "5.5Y",
"price": 180
},
{
"id": "f0d1d36a-f6af-409e-968e-54c1dc104566",
"size": "6.5Y",
"price": 22
}
]
I want to update price property by the ID of an element for ex. "f0d1d36a-f6af-409e-968e-54c1dc104566", so the price would change from 22 to 150 IN ROW WHICH CONTAINS ELEMENT WITH DESIRED ID IN THE COLUMN.
How can I do that?
create table json_update (id integer, json_fld jsonb);
insert into json_update values (1, '[
{
"id": "1",
"size": "5.5Y",
"price": 180
},
{
"id": "f0d1d36a-f6af-409e-968e-54c1dc104566",
"size": "6.5Y",
"price": 22
}
]'
)
;
UPDATE
json_update
SET
json_fld = jsonb_set(json_fld, ARRAY[(idx)::text, 'price'::text], '150'::jsonb)
FROM (
SELECT
(row_number() OVER (ORDER BY t.a ->> 'id') - 1) AS idx,
t.a
FROM (
SELECT
jsonb_array_elements(json_fld)
FROM
json_update) AS t (a)) AS i
WHERE
i.a ->> 'id' = 'f0d1d36a-f6af-409e-968e-54c1dc104566';
select * from json_update ;
id | json_fld
----+---------------------------------------------------------------------------------------------------------------------------
1 | [{"id": "1", "size": "5.5Y", "price": 180}, {"id": "f0d1d36a-f6af-409e-968e-54c1dc104566", "size": "6.5Y", "price": 150}]

PostgresSQL nested jsonb update value of complex key/value pairs

Starting out with JSONB data type and I'm hoping someone can help me out.
I have a table (properties) with two columns (id as primary key and data as jsonb).
The data structure is:
{
"ProductType": "ABC",
"ProductName": "XYZ",
"attributes": [
{
"name": "Color",
"type": "STRING",
"value": "Silver"
},
{
"name": "Case",
"type": "STRING",
"value": "Shells"
},
...
]
}
I would like to update the value of a specific attributes element by name for a row with a given id. For example, for the element with "name"="Case" change the value to "Glass". So it ends up like
{
"ProductType": "ABC",
"ProductName": "XYZ",
"attributes": [
{
"name": "Color",
"type": "STRING",
"value": "Silver"
},
{
"name": "Case",
"type": "STRING",
"value": "Glass"
},
...
]
}
Is this possible with this structure using SQL?
I have created table structure if any of you would like to give it a shot.
dbfiddle
Use the jsonb concatenation operator, ||, to replace keys on the fly:
WITH properties (id, data) AS (
values
(1, '{"ProductType": "ABC","ProductName": "XYZ","attributes": [{"name": "Color","type": "STRING","value": "Silver"},{"name": "Case","type": "STRING","value": "Shells"}]}'::jsonb),
(2, '{"ProductType": "ABC","ProductName": "XYZ","attributes": [{"name": "Color","type": "STRING","value": "Red"},{"name": "Case","type": "STRING","value": "Shells"}]}'::jsonb)
)
SELECT id,
data||
jsonb_build_object(
'attributes',
jsonb_agg(
case
when attribs->>'name' = 'Case' then attribs||'{"value": "Glass"}'::jsonb
else attribs
end
)
) as data
FROM properties m
CROSS JOIN LATERAL JSONB_ARRAY_ELEMENTS(data->'attributes') as a(attribs)
GROUP BY id, data
Updated fiddle

Search inside array of array in JSONB column in Postgresql

I have a JSONB column in my PostgreSQL database. The data looks like this:
{
"cars":
[
{
"id": 1,
"brand": "BMW"
"parts":
[
{
"partId": 5,
"type": "battery"
}
]
},
{
"id": 2,
"brand": "Mercedes"
"parts":
[
{
"partId": 5,
"type": "battery"
},
{
"partId": 6,
"type": "engine"
}
]
}
]
}
Is there any way that I can search for all cars that have a part with type "battery"? How can I search inside of cars array and then inside of the parts array of each car element?
As it's not clear in your question that what output you want. So I am assuming that you want id and brand name in output:
so you try this:
select distinct x.y->>'id', x.y->>'brand'
from test
cross join lateral jsonb_array_elements(data->'cars') x(y)
cross join lateral jsonb_array_elements(x.y->'parts') a(b)
where a.b->>'type'='battery'
DEMO

Can't sort by all array's items?

Postgresq 9.6
json
"availability": [
{
"qty": 25,
"price": 1599,
"is_available": true
},
{
"qty": 72,
"price": 3599,
},
"is_available": true
]
table with column data. Type is jsonb
If I want to sort first array's(availability) item by field "price" I this:
SELECT *
from product prod
WHERE to_tsvector('english', prod.data) ## to_tsquery('gram')
ORDER BY prod.data #> '{availability,0,price}' desc
OK.
But I need to sort all fields "price" in array availability
Smt like this (pseudo code)
SELECT *
from product prod
WHERE to_tsvector('english', prod.data) ## to_tsquery('gram')
ORDER BY prod.data #> '{availability,*,price}' desc
I need to to order by "price" desc.
The result must be
First record of result is second json
"availability": [
{
"qty": 25,
"price": 11599,
"is_available": true
},
{
"qty": 72,
"price": 13599,
},
"is_available": true
]
...
"availability": [
{
"qty": 25,
"price": 1599,
"is_available": true
},
{
"qty": 72,
"price": 3599,
},
"is_available": true
]
Is it possible?
This could be done like this:
select id,
jsonb_set(data, '{availability}',
(select jsonb_agg(item order by (item ->> 'price')::numeric)
from jsonb_array_elements(data -> 'availability') as x(item))
) as data
from product
where ...

Postgresql i have to return 0

I'm trying to make one postgreSQL 9.3 query.The problem is here i have to count all cleaners rated below 4 rating.Here is my query
SELECT count(ratings.score) as below, avg(ratings.score) as avg_rating, cleaners.first_name, cleaners.last_name, cleaners.id, cleaners.created_at
FROM "cleaners"
LEFT JOIN ratings ON ratings.cleaner_id = cleaners.id
GROUP BY cleaners.first_name, cleaners.last_name, cleaners.id, cleaners.created_at
here is the following result:
{
"HTTP_CODE": 200,
"cleaners": [
{
"id": 29,
"rating_below_3_stars": 1,
"avg_rating": "5.0",
"first_name": "asen",
"last_name": "asenov"
},
{
"id": 35,
"rating_below_3_stars": 2,
"avg_rating": "2.5",
"first_name": "Simepl",
"last_name": "cleaner"
}
]
}
The cleaner with id "29" his rating_below_3_stars have to e set to 0
What i want is:
{
"HTTP_CODE": 200,
"cleaners": [
{
"id": 29,
"rating_below_3_stars": 0,
"avg_rating": "5.0",
"first_name": "asen",
"last_name": "asenov"
},
{
"id": 35,
"rating_below_3_stars": 2,
"avg_rating": "2.5",
"first_name": "Simepl",
"last_name": "cleaner"
}
]
}
you count only the ratings below 4
SELECT sum(case when ratings.score<=3 then 1 else null end) as below,
avg(ratings.score) as avg_rating,
cleaners.first_name, cleaners.last_name, cleaners.id,
cleaners.created_at
FROM "cleaners"
LEFT JOIN ratings ON ratings.cleaner_id = cleaners.id
GROUP BY cleaners.first_name, cleaners.last_name, cleaners.id, cleaners.created_at