type recognized openjson open not name json_value invalid built array sql json postgresql indexing jsonb

sql - recognized - Índice para encontrar un elemento en una matriz JSON



open json sql 2014 (1)

Tengo una mesa que se ve así:

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"}]'');

Hay muchas otras columnas que no son relevantes para esta pregunta. Hay una razón para tenerlos almacenados como JSON.

Lo que intento hacer es buscar una pista que tenga un nombre de artista específico (coincidencia exacta).

Estoy usando esta consulta:

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

por ejemplo

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

Sin embargo, esto hace un escaneo completo de la tabla, y no es muy rápido. Traté de crear un índice GIN usando una función names_as_array(artists) , y utilicé ''ARTIST NAME'' = ANY names_as_array(artists) , sin embargo, el índice no se usa y la consulta es significativamente más lenta.


jsonb en Postgres 9.4+

Con el nuevo tipo de datos JSON binarios jsonb , Postgres 9.4 introdujo opciones de índice ampliamente mejoradas . Ahora puede tener un índice GIN en una matriz jsonb directamente:

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

No hay necesidad de una función para convertir la matriz. Esto apoyaría una consulta:

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

@> es el nuevo jsonb "contains" , que puede usar el índice GIN. (No para el tipo json , solo jsonb !)

O utiliza la clase de operador de GIN más especializada y no predeterminada jsonb_path_ops para el índice:

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

Misma consulta

Actualmente jsonb_path_ops solo admite el operador @> . Pero es típicamente mucho más pequeño y más rápido. Hay más opciones de índice, detalles en el manual .

Si los artists solo conservan los nombres tal como se muestran en el ejemplo, sería más eficiente almacenar un valor JSON menos redundante para comenzar: solo los valores como primitivas de texto y la clave redundante pueden estar en el nombre de la columna.

Tenga en cuenta la diferencia entre los objetos JSON y los tipos primitivos:

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);

Consulta:

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

? no funciona para valores de objeto, solo claves y elementos de matriz .
O (más eficiente si los nombres se repiten a menudo):

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

Consulta:

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

json en Postgres 9.3+

Esto debería funcionar con una función 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)'';

Crea este índice funcional :

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

Y usa una consulta como esta. La expresión en la cláusula WHERE debe coincidir con la del índice:

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

Actualizado con comentarios en comentarios. Necesitamos usar operadores de matriz para soportar el índice GIN.
El operador "está contenido por" <@ en este caso.

Notas sobre la volatilidad de la función

Puede declarar su función IMMUTABLE incluso si json_array_elements() no lo era.
La mayoría de las funciones JSON solían ser STABLE , no IMMUTABLE . Hubo una discusión en la lista de piratas informáticos para cambiar eso. La mayoría son IMMUTABLE ahora. Comprobar con:

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%'';

Los índices funcionales solo funcionan con funciones IMMUTABLE .