У меня есть таблица, которая выглядит так:
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)
, однако индекс не используется, и запрос на самом деле значительно медленнее.
Ответы:
jsonb
в Postgres 9.4+С новым двоичным типом данных JSON в
jsonb
Postgres 9.4 были значительно улучшены параметры индексации . Теперь вы можете иметь индекс GINjsonb
непосредственно в массиве: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
!)Или вы используете
jsonb_path_ops
для индекса более специализированный класс операторов GIN, отличный от стандартного: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
функциями.источник
SETOF
нельзя использовать в индексе. Удалив его, я могу создать индекс, однако он не используется планировщиком запросов. Кроме того, json_array_elements и array_aggIMMUTABLE
SET enable_seqscan = off;
(только для целей отладки) stackoverflow.com/questions/14554302/… .