Индекс для поиска элемента в массиве 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'));
обновлено с обратной связью в комментариях. Мы должны использовать операторы массив для поддержки индекса Джина.
Элемент "содержит" оператор<@
в этом случае.примечания по волатильности функций
вы можете объявить функцию
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
функции.