Создание индекса Postgres JSONB для подобъекта массива

У меня есть стол myTable со столбцом JSONB myJsonb со структурой данных, которую я хочу индексировать как:

{
  "myArray": [
    {
      "subItem": {
        "email": "bar@bar.com"
      }
    },
    {
      "subItem": {
        "email": "foo@foo.com"
      }
    }
  ]
}

Я хочу выполнять индексированные запросы на email лайк:

SELECT *
FROM mytable
WHERE 'foo@foo.com' IN (
  SELECT lower(
      jsonb_array_elements(myjsonb -> 'myArray')
      -> 'subItem'
      ->> 'email'
  )
);

Как мне создать индекс Postgres JSONB для этого?

1 ответ

Решение

Если вам не нужно lower() там, запрос может быть простым и эффективным:

SELECT *
FROM   mytable
WHERE  myjsonb -> 'myArray' @> '[{"subItem": {"email": "foo@foo.com"}}]'

Поддерживается jsonb_path_ops индекс:

CREATE INDEX mytable_myjsonb_gin_idx ON mytable
USING  gin ((myjsonb -> 'myArray') jsonb_path_ops);

Но в совпадении учитывается регистр.

Без учета регистра!

Если вам нужен поиск, чтобы соответствовать независимо от случая, вещи становятся более сложными.

Вы можете использовать этот запрос, похожий на ваш оригинал:

SELECT *
FROM   t
WHERE  EXISTS (
   SELECT 1
   FROM   jsonb_array_elements(myjsonb -> 'myArray') arr
   WHERE  lower(arr #>>'{subItem, email}') = 'foo@foo.com'
   );

Но я не могу придумать хороший способ использовать индекс для этого.

Вместо этого я бы использовал индекс выражения, основанный на функции, извлекающей массив писем в нижнем регистре:

Функция:

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';

Индекс:

CREATE INDEX mytable_email_arr_idx ON mytable
USING  gin (f_jsonb_arr_lower(myjsonb -> 'myArray', 'subItem', 'email') jsonb_path_ops);

Запрос:

SELECT *
FROM   mytable 
WHERE  f_jsonb_arr_lower(myjsonb -> 'myArray', 'subItem', 'email') @> '"foo@foo.com"';

Хотя это работает с нетипизированным строковым литералом или с фактическим jsonb значения, он перестает работать, если вы передаете text или же varchar (как в готовом заявлении). Postgres не знает, как разыгрывать, потому что ввод неоднозначен. Вам нужно явное приведение в этом случае:

... @> '"foo@foo.com"'::text::jsonb;

Или передайте простую строку без двойных кавычек и выполните преобразование в jsonb в Postgres:

... @> to_jsonb('foo@foo.com'::text);

Связанные с более подробным объяснением:

Другие вопросы по тегам