Consulta de elementos de matriz dentro do tipo JSON

118

Estou tentando testar o jsontipo no PostgreSQL 9.3.
Eu tenho uma jsoncoluna chamada dataem uma tabela chamada reports. O JSON se parece com isto:

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

Eu gostaria de consultar a tabela para todos os relatórios que correspondem ao valor 'src' na matriz 'objetos'. Por exemplo, é possível consultar o banco de dados para todos os relatórios correspondentes 'src' = 'foo.png'? Escrevi com sucesso uma consulta que pode corresponder a "background":

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

Mas "objects", uma vez que tem uma matriz de valores, não consigo escrever algo que funcione. É possível consultar o banco de dados para todos os relatórios correspondentes 'src' = 'foo.png'? Procurei por essas fontes, mas ainda não consigo:

Eu também tentei coisas assim, mas sem sucesso:

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

Não sou um especialista em SQL, então não sei o que estou fazendo de errado.

Pacothelovetaco
fonte

Respostas:

214

json no Postgres 9.3+

Desaninhe a matriz JSON com a função json_array_elements()em uma junção lateral na FROMcláusula e teste seus 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';

O CTE ( WITHconsulta) apenas substitui uma tabela reports.
Ou equivalente para apenas um único nível de aninhamento:

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

->>, ->E #>os operadores são explicados no manual.

Ambas as consultas usam um implícito JOIN LATERAL.

SQL Fiddle.

Resposta intimamente relacionada:

jsonb no Postgres 9.4+

Use o equivalente jsonb_array_elements().

Melhor ainda, use o novo operador "contém" @>(melhor em combinação com um índice GIN correspondente na expressão 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"}]';

Como a chave objectscontém um array JSON , precisamos combinar a estrutura no termo de pesquisa e envolver o elemento do array em colchetes também. Remova os colchetes da matriz ao pesquisar um registro simples.

Explicação detalhada e mais opções:

Erwin Brandstetter
fonte
1
@pacothelovetaco: adicionada uma atualização para jsonb/ pg 9.4. À parte: para o caso simples (1 nível de aninhamento), o ->operador também faz o truque jsonna página 9.3.
Erwin Brandstetter
1
@pacothelovetaco, para a página 9.3, '#>' não é o molho secreto, '->' seria adequado para o seu caso, pois também retorna o objec json. '#>' seria mais útil no caso de caminho json aninhado, pois permite que você especifique facilmente o caminho em '{}'
Gob00st
1
@> '[{"src": "foo.png"}]'; funcionam bem em condições onde, mas como excluir um objeto particular como este? Eu não sei o índice deste objeto. eu quero excluir por valor-chave.
Pranay Soni
1
@PranaySoni: Faça a nova pergunta como pergunta . Os comentários não são o lugar. Você sempre pode criar um link para este para contexto.
Erwin Brandstetter
caro @ErwinBrandstetter, é possível encontrar os dois documentos por correspondência parcial? Por exemplo, eu gostaria de obter os dois registros algo como '[{"src": ". Png"}]'
Pyrejkee
8

Crie uma tabela com coluna como tipo json

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

Agora vamos inserir os dados 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}');

Agora vamos fazer algumas consultas para buscar dados

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

Você deve ter notado que os resultados vêm com vírgulas invertidas (") e colchetes ([])

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

Agora, para recuperar apenas os valores, basta usar ->>

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
fonte
22
Este é um ruído formatado de forma agradável, sem conexão discernível com a pergunta.
Erwin Brandstetter
4
Eu achei isso útil. Mostra como fazer drill na matriz em um jsonb
GavinBelson
0

selecione dados -> 'objetos' -> 0 -> 'src' como SRC da tabela onde dados -> 'objetos' -> 0 -> 'src' = 'foo.png'

anand shukla
fonte
2
Isso seria útil SOMENTE SE você conhece o índice, que era 0.
Buyut Joko Rivai
sim, mas há uma maneira de explodir o objeto array que mapeará a linha e podemos usar isso. Corrija-me se eu estiver errada.
anand shukla
não é uma boa solução, pois você não pode ter certeza, "src" está na posição 0.
simUser