Consulta de elementos de matriz dentro del tipo JSON

118

Estoy tratando de probar el jsontipo en PostgreSQL 9.3.
Tengo una jsoncolumna llamada dataen una tabla llamada reports. El JSON se parece a esto:

{
  "objects": [
    {"src":"foo.png"},
    {"src":"bar.png"}
  ],
  "background":"background.png"
}

Me gustaría consultar la tabla para todos los informes que coincidan con el valor 'src' en la matriz 'objetos'. Por ejemplo, ¿es posible consultar la base de datos para todos los informes que coincidan 'src' = 'foo.png'? Escribí con éxito una consulta que puede coincidir con "background":

SELECT data AS data FROM reports where data->>'background' = 'background.png'

Pero como "objects"tiene una matriz de valores, parece que no puedo escribir algo que funcione. ¿Es posible consultar la base de datos para todos los informes que coincidan 'src' = 'foo.png'? Revisé estas fuentes pero todavía no puedo obtenerlo:

También probé cosas como esta, pero fue en vano:

SELECT json_array_elements(data->'objects') AS data from reports
WHERE  data->>'src' = 'foo.png';

No soy un experto en SQL, así que no sé qué estoy haciendo mal.

pacothelovetaco
fuente

Respuestas:

215

json en Postgres 9.3+

Desanida la matriz JSON con la función json_array_elements()en una unión lateral en la FROMcláusula y pruebe sus elementos:

WITH reports(data) AS (
   VALUES ('{"objects":[{"src":"foo.png"}, {"src":"bar.png"}]
           , "background":"background.png"}'::json)
   ) 
SELECT *
FROM   reports r, json_array_elements(r.data#>'{objects}') obj
WHERE  obj->>'src' = 'foo.png';

El CTE ( WITHconsulta) simplemente sustituye a una tabla reports.
O equivalente para un solo nivel de anidamiento:

SELECT *
FROM   reports r, json_array_elements(r.data->'objects') obj
WHERE  obj->>'src' = 'foo.png';

->>, ->Y #>los operadores se explican en el manual.

Ambas consultas utilizan un implícito JOIN LATERAL.

Violín SQL.

Respuesta estrechamente relacionada:

jsonb en Postgres 9.4+

Utilice el equivalente jsonb_array_elements().

Mejor aún, use el nuevo operador "contiene" @>(mejor en combinación con un índice GIN coincidente en la expresión data->'objects'):

CREATE INDEX reports_data_gin_idx ON reports
USING gin ((data->'objects') jsonb_path_ops);

SELECT * FROM reports WHERE data->'objects' @> '[{"src":"foo.png"}]';

Dado que la clave objectscontiene una matriz JSON , necesitamos hacer coincidir la estructura en el término de búsqueda y también envolver el elemento de la matriz entre corchetes. Elimine los corchetes de la matriz cuando busque un registro simple.

Explicación detallada y más opciones:

Erwin Brandstetter
fuente
1
@pacothelovetaco: agregó una actualización para jsonb/ pg 9.4. Aparte: para el caso simple (1 nivel de anidamiento), el ->operador también hace el truco jsonen la página 9.3.
Erwin Brandstetter
1
@pacothelovetaco, para la página 9.3, '#>' no es la salsa secreta, '->' estaría bien para su caso, ya que también devuelve el objeto json. '#>' sería más útil en el caso de la ruta json anidada, ya que le permite especificar fácilmente la ruta en el '{}'
Gob00st
1
@> '[{"src": "foo.png"}]'; funciona bien en la condición, pero ¿cómo eliminar un objeto en particular como este? No sé el índice de este objeto. quiero eliminar por valor clave.
Pranay Soni
1
@PranaySoni: Haga la nueva pregunta como pregunta . Los comentarios no son el lugar. Siempre puede vincular a este para el contexto.
Erwin Brandstetter
querido @ErwinBrandstetter, ¿es posible encontrar ambos documentos por coincidencia parcial? Por ejemplo, me gustaría obtener ambos registros algo así como '[{"src": ". Png"}]'
Pyrejkee
8

Crea una tabla con columna como tipo json

CREATE TABLE friends ( id serial primary key, data jsonb);

Ahora insertemos datos json

INSERT INTO friends(data) VALUES ('{"name": "Arya", "work": ["Improvements", "Office"], "available": true}');
INSERT INTO friends(data) VALUES ('{"name": "Tim Cook", "work": ["Cook", "ceo", "Play"], "uses": ["baseball", "laptop"], "available": false}');

Ahora hagamos algunas consultas para obtener datos.

select data->'name' from friends;
select data->'name' as name, data->'work' as work from friends;

Es posible que haya notado que los resultados vienen con comas invertidas (") y corchetes ([])

    name    |            work            
------------+----------------------------
 "Arya"     | ["Improvements", "Office"]
 "Tim Cook" | ["Cook", "ceo", "Play"]
(2 rows)

Ahora para recuperar solo los valores, use ->>

select data->>'name' as name, data->'work'->>0 as work from friends;
select data->>'name' as name, data->'work'->>0 as work from friends where data->>'name'='Arya';
Sandip Debnath
fuente
22
Se trata de un ruido con un formato agradable sin una conexión perceptible con la pregunta.
Erwin Brandstetter
4
Encontré esto útil. Muestra cómo profundizar en la matriz en un jsonb
GavinBelson
0

seleccione datos -> 'objetos' -> 0 -> 'src' como SRC de la tabla donde datos -> 'objetos' -> 0 -> 'src' = 'foo.png'

anand shukla
fuente
2
Esto sería útil SOLO SI conoce el índice, que era 0.
Buyut Joko Rivai
sí, pero hay una forma de explotar el objeto de matriz que mapeará las filas y podemos usar eso. Corrígeme si estoy equivocado.
anand shukla
no es una buena solución ya que no puedes estar seguro, "src" está en la posición 0.
simUser