I have table myTable
with a JSONB column myJsonb
with a data structure that I want to index like:
{
\"myArray\": [
{
\"
If you don't need the lower()
in there, the query can be simple and efficient:
SELECT *
FROM mytable
WHERE myjsonb -> 'myArray' @> '[{"subItem": {"email": "foo@foo.com"}}]'
Supported by a jsonb_path_ops
index:
CREATE INDEX mytable_myjsonb_gin_idx ON mytable
USING gin ((myjsonb -> 'myArray') jsonb_path_ops);
But the match is case-sensitive.
If you need the search to match disregarding case, things get more complex.
You could use this query, similar to your original:
SELECT *
FROM t
WHERE EXISTS (
SELECT 1
FROM jsonb_array_elements(myjsonb -> 'myArray') arr
WHERE lower(arr #>>'{subItem, email}') = 'foo@foo.com'
);
But I can't think of a good way to use an index for this.
Instead, I would use an expression index based on a function extracting an array of lower-case emails:
Function:
CREATE OR REPLACE FUNCTION f_jsonb_arr_lower(_j jsonb, VARIADIC _path text[])
RETURNS jsonb LANGUAGE sql IMMUTABLE AS
'SELECT jsonb_agg(lower(elem #>> _path)) FROM jsonb_array_elements(_j) elem';
Index:
CREATE INDEX mytable_email_arr_idx ON mytable
USING gin (f_jsonb_arr_lower(myjsonb -> 'myArray', 'subItem', 'email') jsonb_path_ops);
Query:
SELECT *
FROM mytable
WHERE f_jsonb_arr_lower(myjsonb -> 'myArray', 'subItem', 'email') @> '"foo@foo.com"';
While this works with an untyped string literal or with actual jsonb
values, it stops working if you pass text
or varchar
(like in a prepared statement). Postgres does not know how to cast because the input is ambiguous. You need an explicit cast in this case:
... @> '"foo@foo.com"'::text::jsonb;
Or pass a simple string without enclosing double quotes and do the conversion to jsonb
in Postgres:
... @> to_jsonb('foo@foo.com'::text);
Related, with more explanation: