Tengo una mesa que se parece a esto:
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 varias otras columnas que no son relevantes para esta pregunta. Hay una razón para almacenarlos como JSON.
Lo que estoy tratando de 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. Intenté crear un índice GIN usando una función names_as_array(artists)
y lo usé 'ARTIST NAME' = ANY names_as_array(artists)
, sin embargo, el índice no se usa y la consulta es significativamente más lenta.
Respuestas:
jsonb
en Postgres 9.4+Con el nuevo tipo de datos JSON binario
jsonb
, Postgres 9.4 introdujo opciones de índice ampliamente mejoradas . Ahora puede tener un índice GIN en unajsonb
matriz directamente:CREATE TABLE tracks (id serial, artists jsonb); CREATE INDEX tracks_artists_gin_idx ON tracks USING gin (artists);
No se necesita una función para convertir la matriz. Esto apoyaría una consulta:
SELECT * FROM tracks WHERE artists @> '[{"name": "The Dirty Heads"}]';
@>
siendo el nuevojsonb
operador "contiene" , que puede utilizar el índice GIN. (¡No para tipojson
, solojsonb
!)O usa la clase de operador 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 es compatible con el@>
operador. Pero normalmente es mucho más pequeño y rápido. Hay más opciones de índice, detalles en el manual .Si
artists
solo contiene nombres como se muestra en el ejemplo, sería más eficiente almacenar un valor JSON menos redundante para empezar: 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 con frecuencia):
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
IMMUTABLE
función :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)';
Cree 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 de la
WHERE
cláusula debe coincidir con la del índice:SELECT * FROM tracks WHERE '{"The Dirty Heads"}'::text[] <@ (json2arr(artists, 'name'));
Actualizado con retroalimentación en comentarios. Necesitamos usar operadores de matriz para admitir 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 sijson_array_elements()
no lofue.La mayoría de las
JSON
funciones solían ser únicasSTABLE
, noIMMUTABLE
. Hubo una discusión en la lista de hackers para cambiar eso. La mayoría lo sonIMMUTABLE
ahora. Comprueba 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
IMMUTABLE
funciones.fuente
SETOF
no se puede usar en un índice. Al eliminar eso, puedo crear el índice, sin embargo, el planificador de consultas no lo usa. Además, tanto json_array_elements como array_agg sonIMMUTABLE
SET enable_seqscan = off;
(solo con fines de depuración) stackoverflow.com/questions/14554302/… .