Consultando JSONB no PostgreSQL

14

Eu tenho uma tabela, personsque contém duas colunas, uma ide uma datacoluna baseada em JSONB (esta tabela foi criada apenas para fins de demonstração para brincar com o suporte a JSON do PostgreSQL).

Agora, suponha que ele contenha dois registros:

1, { name: 'John', age: 30 }
2, { name: 'Jane', age: 20 }

Agora, suponho que eu queira obter o nome de todas as pessoas com mais de 25 anos. O que tentei é:

select data->'name' as name from persons where data->'age' > 25

Infelizmente, isso resulta em um erro. Eu posso resolvê-lo usando em ->>vez de ->, mas as comparações não funcionam mais como o esperado, pois não são comparados os números, mas suas representações como seqüências de caracteres:

select data->'name' as name from persons where data->>'age' > '25'

Eu então descobri que realmente posso resolver o problema usando ->e convertido para int:

select data->'name' as name from persons where cast(data->'age' as int) > 25

Isso funciona, mas não é tão bom que eu precise saber o tipo real (o tipo de agedocumento JSON é numberassim mesmo, então por que o PostgreSQL não pode descobrir isso sozinho?).

Eu então descobri que, se eu converter manualmente para o textuso da ::sintaxe, tudo funcionará conforme o esperado também - embora agora possamos comparar as strings novamente.

select data->'name' as name from persons where data->'age'::text > '25'

Se eu tentar isso com o nome em vez da idade, não funcionará:

select data->'name' as name from persons where data->'name'::text > 'Jenny'

Isso resulta em um erro:

sintaxe de entrada inválida para o tipo json

Obviamente, não tenho nada aqui. Infelizmente, é muito difícil encontrar exemplos reais do uso de JSON com o PostgreSQL.

Alguma dica?

Golo Roden
fonte
11
Em data->'name'::text, você está convertendo a 'name'string em texto, não no resultado. Você não recebe um erro ao comparar '25'porque 25é um literal JSON válido; mas Jennynão é (embora "Jenny"seria).
Chirlu
Obrigado, essa é a solução :-). Eu confundi 'Jenny'com '"Jenny"'.
Golo Roden

Respostas:

14

Isso não funciona porque está tentando converter um jsonbvalor para integer.

select data->'name' as name from persons where cast(data->'age' as int) > 25

Isso realmente funcionaria:

SELECT data->'name' AS name FROM persons WHERE cast(data->>'age' AS int) > 25;

Ou mais curto:

SELECT data->'name' AS name FROM persons WHERE (data->>'age')::int > 25;

E isto:

SELECT data->'name' AS name FROM persons WHERE data->>'name' > 'Jenny';

Parece que a confusão com os dois operadores ->e->> e precedência do operador . O elenco ::se liga mais forte que os operadores json (b).

Descobrir o tipo dinamicamente

Esta é a parte mais interessante da sua pergunta:

de qualquer forma, o tipo de idade no documento JSON é o número; então, por que o PostgreSQL não pode descobrir isso sozinho?

SQL é uma linguagem estritamente digitada, não permite que a mesma expressão seja avaliada integerem uma linha e textna seguinte. Mas como você está interessado apenas no booleanresultado do teste, é possível contornar essa restrição com uma CASEexpressão que bifurca dependendo do resultado de jsonb_typeof():

SELECT data->'name'
FROM   persons
WHERE  CASE jsonb_typeof(data->'age')
        WHEN 'number'  THEN (data->>'age')::numeric > '25' -- treated as numeric
        WHEN 'string'  THEN data->>'age' > 'age_level_3'   -- treated as text
        WHEN 'boolean' THEN (data->>'age')::bool           -- use boolean directly (example)
        ELSE FALSE                                         -- remaining: array, object, null
       END;

Uma cadeia de caracteres sem tipo literal à direita do >operador é coagida automaticamente ao respectivo tipo de valor à esquerda. Se você colocar um valor digitado lá, o tipo deverá corresponder ou será necessário convertê-lo explicitamente - a menos que exista uma conversão implícita adequada registrada no sistema.

Se você souber que todos os valores numéricos são realmente integer, também pode:

... (data->>'age')::int > 25 ...
Erwin Brandstetter
fonte
qual é a expressão do núcleo sqlalchemy para a comparação acima da instrução select, por exemplo. s = select ([edições]). where (issues.c.id == mid) .select_from (issues, ..... externaljoin (issues.c.data ['type_id'] == mtypes.c.id) ) ... Aqui issues.c.data tipo de dados jsonb e está sendo comparada com mtypes.c.id do tipo inteiro
user956424