PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Índice para encontrar um elemento em uma matriz JSON

jsonb no Postgres 9.4+


O tipo de dados JSON binário jsonb melhora amplamente as opções de índice. Agora você pode ter um índice GIN em um jsonb matriz diretamente:
CREATE TABLE tracks (id serial, artists jsonb);  -- !
CREATE INDEX tracks_artists_gin_idx ON tracks USING gin (artists);

Não há necessidade de uma função para converter a matriz. Isso suportaria uma consulta:
SELECT * FROM tracks WHERE artists @> '[{"name": "The Dirty Heads"}]';

@> sendo o jsonb operador "contém", que pode usar o índice GIN. (Não para json , apenas jsonb !)

Ou você usa a classe de operador GIN mais especializada e não padrão jsonb_path_ops para o índice:
CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (artists jsonb_path_ops);  -- !

Mesma consulta.

Atualmente jsonb_path_ops suporta apenas o @> operador. Mas normalmente é muito menor e mais rápido. Há mais opções de índice, detalhes no manual .

Se a coluna artists contém apenas os nomes exibidos no exemplo, seria mais eficiente armazenar apenas os valores como texto JSON primitivos e a chave redundante pode ser o nome da coluna.

Observe a diferença entre objetos JSON e tipos primitivos:
  • Usando índices no array json no PostgreSQL
Faixas
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);

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

? não funciona para objetos valores , apenas chaves e elementos de matriz .

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

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

Mais eficiente se os nomes forem altamente duplicados.

json no Postgres 9.3+


Isso deve funcionar com um IMMUTABLE função :
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)';

Crie este índice funcional :
CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (json2arr(artists, 'name'));

E use uma consulta assim. A expressão no WHERE A cláusula deve corresponder à do índice:
SELECT * FROM tracks
WHERE  '{"The Dirty Heads"}'::text[] <@ (json2arr(artists, 'name'));

Atualizado com feedback nos comentários. Precisamos usar operadores de matriz para suportar o índice GIN.
O operador "está contido por" <@ nesse caso.

Notas sobre a volatilidade da função


Você pode declarar sua função IMMUTABLE mesmo se json_array_elements() não é não era.
A maioria dos JSON funções costumavam ser apenas STABLE , não IMMUTABLE . Houve uma discussão na lista de hackers para mudar isso. A maioria é IMMUTABLE agora. Verificar com:
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%';

Índices funcionais só funcionam com IMMUTABLE funções.