Consultando JSONB en PostgreSQL

13

Tengo una tabla, personsque contiene dos columnas, una idy una datacolumna basada en JSONB (esta tabla acaba de hacerse con fines demostrativos para jugar con el soporte JSON de PostgreSQL).

Ahora, se supone que contiene dos registros:

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

Ahora, supongo que quiero obtener el nombre de cada persona mayor de 25 años. Lo que he intentado es:

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

Desafortunadamente, esto da como resultado un error. Puedo resolverlo usando en ->>lugar de ->, pero las comparaciones ya no funcionan como se esperaba, ya que no se comparan los números, sino sus representaciones como cadenas:

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

Luego descubrí que realmente puedo resolver el problema usando ->y un elenco para int:

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

Esto funciona, pero no es tan bueno que tenga que saber el tipo real (el tipo de ageen el documento JSON es de numbertodos modos, entonces, ¿por qué PostgreSQL no puede resolver eso por sí mismo?).

Luego descubrí que si convierto manualmente el textuso de la ::sintaxis, todo funciona como se esperaba, aunque ahora estamos comparando cadenas nuevamente.

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

Si luego intento esto con el nombre en lugar de la edad, no funciona:

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

Esto da como resultado un error:

sintaxis de entrada no válida para el tipo json

Obviamente, no consigo algo aquí. Desafortunadamente, es bastante difícil encontrar ejemplos del mundo real del uso de JSON con PostgreSQL.

¿Alguna pista?

Golo Roden
fuente
1
En data->'name'::text, está convirtiendo la 'name'cadena a texto, no el resultado. No obtiene un error al comparar '25'porque 25es un literal JSON válido; pero Jennyno lo es (aunque "Jenny"lo sería).
chirlu
Gracias, esa es la solución :-). Me confundí 'Jenny'con '"Jenny"'.
Golo Roden

Respuestas:

14

Esto no funciona porque está tratando de emitir un jsonbvalor integer.

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

Esto realmente funcionaría:

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

O más corto:

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

Y esto:

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

Parece que la confusión con los dos operadores ->y->> y la precedencia de operadores . El reparto se ::une más fuerte que los operadores json (b).

Figura tipo de forma dinámica

Esta es la parte más interesante de tu pregunta:

el tipo de edad en el documento JSON es el número de todos modos, entonces, ¿por qué PostgreSQL no puede resolver eso por sí mismo?

SQL es un lenguaje estrictamente tipado, no permite evaluar la misma expresión integeren una fila y texten la siguiente. Pero como solo está interesado en el booleanresultado de la prueba, puede sortear esta restricción con una CASEexpresión que se bifurca dependiendo del 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;

Un literal de cadena sin tipo a la derecha del >operador se coacciona al tipo respectivo del valor a la izquierda automáticamente. Si coloca un valor escrito allí, el tipo tiene que coincidir o debe emitirlo explícitamente, a menos que haya una conversión implícita adecuada registrada en el sistema.

Si sabe que todos los valores numéricos son en realidad integer, también puede:

... (data->>'age')::int > 25 ...
Erwin Brandstetter
fuente
¿Cuál es la expresión principal de sqlalchemy para la comparación anterior de la instrucción select, por ejemplo? s = select ([issues]). where (issues.c.id == mid) .select_from (issues, ..... outsidejoin (issues.c.data ['type_id'] == mtypes.c.id) ) ... Aquí emite.c.data tipo de datos jsonb y se está comparando con mtypes.c.id de tipo entero
user956424