web-dev-qa-db-fra.com

Requête JSONB dans PostgreSQL

J'ai une table, persons, qui contient deux colonnes, une id et une colonne data basée sur JSONB (cette table vient d'être créée à des fins de démonstration pour jouer avec Prise en charge JSON de PostgreSQL).

Maintenant, supposons qu'il contienne deux enregistrements:

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

Maintenant, je suppose que je veux obtenir le nom de toute personne âgée de plus de 25 ans. Ce que j'ai essayé, c'est:

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

Malheureusement, cela entraîne une erreur. Je peux le résoudre en utilisant ->> au lieu de ->, mais les comparaisons ne fonctionnent plus comme prévu, car non pas les nombres sont comparés, mais leurs représentations sous forme de chaînes:

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

J'ai ensuite compris que je pouvais réellement résoudre le problème en utilisant -> et un cast en int:

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

Cela fonctionne, mais ce n'est pas si agréable que je doive connaître le type réel (le type de age dans le document JSON est number de toute façon, alors pourquoi PostgreSQL ne peut-il pas le comprendre par lui-même ?).

J'ai ensuite compris que si je convertissais manuellement en text en utilisant le :: syntaxe, tout fonctionne comme prévu, même si nous comparons à nouveau les chaînes.

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

Si j'essaye ensuite avec le nom au lieu de l'âge, cela ne fonctionne pas:

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

Il en résulte une erreur:

syntaxe d'entrée non valide pour le type json

De toute évidence, je n'ai rien ici. Malheureusement, il est assez difficile de trouver des exemples concrets d'utilisation de JSON avec PostgreSQL.

Des indices?

13
Golo Roden

Cela ne fonctionne pas car il essaie de convertir une valeur jsonb en integer.

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

This fonctionnerait en fait:

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

Ou plus court:

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

Et ça:

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

Semble comme une confusion avec les deux opérateurs -> Et ->> et priorité de l'opérateur . Le cast :: Se lie plus fort que les opérateurs json (b).

Comprendre le type dynamiquement

Voici la partie la plus intéressante de votre question:

le type d'âge dans le document JSON est de toute façon numéro, alors pourquoi PostgreSQL ne peut-il pas le comprendre par lui-même?

SQL est un langage strictement typé, il ne permet pas à la même expression d'évaluer integer sur une ligne et text sur la suivante. Mais comme vous n'êtes intéressé que par le résultat boolean du test, vous pouvez contourner cette restriction avec une expression CASE qui bifurque en fonction du résultat 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 littéral de chaîne non typé à droite de l'opérateur > Est automatiquement contraint au type respectif de la valeur à gauche. Si vous y mettez une valeur typée, le type doit correspondre ou vous devez le cast explicitement - à moins qu'il n'y ait un cast implicite adéquat enregistré dans le système.

Si vous savez que toutes les valeurs numériques sont en fait integer, vous pouvez également:

... (data->>'age')::int > 25 ...
14
Erwin Brandstetter