Индекс для поиска элемента в массиве 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))
Однако, это делает полное сканирование таблицы, и это не очень быстро. Я пытался создать индекс GIN с помощью функции names_as_array(artists)
и использовал 'ARTIST NAME' = ANY names_as_array(artists)
Однако индекс не используется, и запрос на самом деле значительно медленнее.
1 ответ
jsonb
в Postgres 9,4+
С новым двоичным типом данных JSON jsonb
В Postgres 9.4 были значительно улучшены параметры индекса. Теперь вы можете иметь индекс GIN на 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
Оператор "содержит", который может использовать индекс GIN. (Не для типа 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'));
Обновлено с отзывами в комментариях. Нам нужно использовать операторы массива для поддержки индекса GIN.
Оператор "содержится" <@
в этом случае.
Примечания о волатильности функций
Вы можете объявить свою функцию IMMUTABLE
даже если json_array_elements()
не было
Наиболее JSON
функции раньше были только STABLE
не IMMUTABLE
, Было обсуждение в списке хакеров, чтобы изменить это. Большинство из них 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
функции.