Reputation: 1179
Using PostgreSQL 14 I have a table 'fruits' with a JSONB column named 'items' containing this sample data:
[{"anz": 4, "typ": "Banana"}, {"anz": 5, "typ": "Apple"}, {"anz": 2, "typ": "Grapefruit"}]
[{"anz": 1, "typ": "Banana"}, {"anz": 1, "typ": "Apple"}, {"anz": 3, "typ": "Grapefruit"}]
this works:
SELECT * FROM fruits WHERE items @> '[{"typ":"Apple"}]';
SELECT * FROM fruits WHERE (items -> 0 ->> 'lvl')::int > 4;
Bu now I would like to fetch only the record where Apple has 'anz > 3'. Combining the WHERE clauses from the queries above doesn't fit of course. What's the appropriate SQL?
Expected Output should be the identified record (SELECT * FROM .. WHERE ..)
Upvotes: 1
Views: 1001
Reputation: 26322
You're using jsonb
, so you have the full support of JSONPath
expressions: demo at db<>fiddle
select * from fruits where items @? '$[*] ? (@.typ=="Apple" && @.anz>3)';
id | items |
---|---|
1 | [{"anz": 4, "typ": "Banana"}, {"anz": 5, "typ": "Apple"}, {"anz": 2, "typ": "Grapefruit"}] |
@?
operator checks if the jsonb
returns anything for the jsonpath expression$
is your top-level thing[*]
checks all elements of the array?
is a filter, like a where
@
is the element considered by the filter&&
is the logical AND
operatorYou can speed this up by adding an index:
create index on fruits using gin(items jsonb_path_ops);
The doc isn't immediately clear on whether your expression should be able to use it:
For these operators, a GIN index extracts clauses of the form
accessors_chain = constant
out of thejsonpath
pattern, and does the index search based on the keys and values mentioned in these clauses. The accessors chain may include.key
,[*]
, and[index]
accessors. Thejsonb_ops
operator class also supports.*
and.**
accessors, but thejsonb_path_ops
operator class does not.
But it does.
Upvotes: 2
Reputation: 16063
One option is to extract the array into elements by using json_array_elements()
then apply your conditions:
SELECT i.value as item
FROM fruits f
CROSS JOIN jsonb_array_elements(items) i
WHERE f.items @> '[{"typ":"Apple"}]'
AND (i->>'anz')::int > 3
AND i->>'typ' = 'Apple';
Results :
item
{"anz": 5, "typ": "Apple"}
Upvotes: 1