Индекс для поиска элемента в массиве JSON

у меня есть таблица, которая выглядит так:

CREATE TABLE tracks (id SERIAL, artists JSON);

INSERT INTO tracks (id, artists) 
  VALUES (1, '[{"name": "blink-182"}]');

INSERT INTO tracks (id, artists) 
  VALUES (2, '[{"name": "The Dirty Heads"}, {"name": "Louis Richards"}]');

есть несколько других столбцов, которые не имеют отношения к этому вопросу. Есть причина хранить их как JSON.

то, что я пытаюсь сделать, это поиск дорожки, которая имеет определенный имя автора (точное соответствие).

я использую этот запрос:

SELECT * FROM tracks 
  WHERE 'ARTIST NAME' IN
    (SELECT value->>'name' FROM json_array_elements(artists))
SELECT * FROM tracks
  WHERE 'The Dirty Heads' IN 
    (SELECT value->>'name' FROM json_array_elements(artists))

однако, это делает полное сканирование таблицы, и это не очень быстро. Я пытался создать индекс джина с использованием функции names_as_array(artists), и использовать 'ARTIST NAME' = ANY names_as_array(artists), однако индекс не используется, и запрос на самом деле значительно медленнее.

1 ответов


jsonb в Postgres 9.4+

С Новым двоичным типом данных JSON jsonb, и Postgres 9.4 введен значительно улучшенные параметры индекса. Теперь вы можете иметь индекс джина на jsonb массив напрямую:

CREATE TABLE tracks (id serial, artists jsonb);
CREATE INDEX tracks_artists_gin_idx ON tracks USING gin (artists);

нет необходимости в функции для преобразования массива. Это будет поддерживать запрос:

SELECT * FROM tracks WHERE artists @> '[{"name": "The Dirty Heads"}]';

@> новая jsonb "содержит" оператор, который может использовать индекс Джина. (Не для типа json, только jsonb!)

или вы используете более специализированный, нестандартный класс оператора GIN jsonb_path_ops для индекса:

CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (artists jsonb_path_ops);

один и тот же запрос.

в настоящее время jsonb_path_ops поддерживает только @> оператора. Но обычно он намного меньше и быстрее. Есть больше вариантов индекса,подробности в руководство.


если artists содержит только имена, как показано в Примере, было бы более эффективно хранить менее избыточное значение JSON для начала: только значения текст примитивы и резервные ключ может быть в имени столбца.

обратите внимание на разницу между объектами JSON и примитивными типами:

CREATE TABLE tracks (id serial, artistnames jsonb);
INSERT INTO tracks  VALUES (2, '["The Dirty Heads", "Louis Richards"]');

CREATE INDEX tracks_artistnames_gin_idx ON tracks USING gin (artistnames);

запрос:

SELECT * FROM tracks WHERE artistnames ? 'The Dirty Heads';

? не работает для объекта значения, просто ключи и элементы массива.
Или (более эффективно, если имена часто повторяются):

CREATE INDEX tracks_artistnames_gin_idx ON tracks
USING  gin (artistnames jsonb_path_ops);

запрос:

SELECT * FROM tracks WHERE artistnames @> '"The Dirty Heads"'::jsonb;

json в Postgres 9.3+

это должно работать с тегом IMMUTABLE функции:

CREATE OR REPLACE FUNCTION json2arr(_j json, _key text)
  RETURNS text[] LANGUAGE sql IMMUTABLE AS
'SELECT ARRAY(SELECT elem->>_key FROM json_array_elements(_j) elem)';

создать этот функциональное индекс:

CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (json2arr(artists, 'name'));

и использовать запрос такой. Выражение в WHERE предложение должно соответствовать одному в индексе:

SELECT * FROM tracks
WHERE  '{"The Dirty Heads"}'::text[] <@ (json2arr(artists, 'name'));

обновлено с обратной связью в комментариях. Нам нужно использовать операторы массив для поддержки индекса Джина.
The "содержит" оператор <@ в этом случае.

заметки о волатильности функция

вы можете объявить функцию IMMUTABLE даже если json_array_elements() не не было.
Большинство JSON функции только STABLE, а не IMMUTABLE. в списке хакеров была дискуссия, чтобы изменить это. большинство из них . Проверьте с:

SELECT p.proname, p.provolatile
FROM   pg_proc p
JOIN   pg_namespace n ON n.oid = p.pronamespace
WHERE  n.nspname = 'pg_catalog'
AND    p.proname ~~* '%json%';

функциональные индексы работают только с IMMUTABLE функции.