Abfrage von JSONB in ​​PostgreSQL

12

Ich habe eine Tabelle, personsdie zwei Spalten enthält, eine idund eine JSONB-basierte dataSpalte (diese Tabelle wurde nur zu Demonstrationszwecken erstellt, um mit der JSON-Unterstützung von PostgreSQL herumzuspielen).

Angenommen, es enthält zwei Datensätze:

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

Angenommen, ich möchte den Namen jeder Person erfahren, die älter als 25 Jahre ist. Was ich versucht habe, ist:

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

Dies führt leider zu einem Fehler. Ich kann es lösen, indem ich ->>anstelle von verwende ->, aber dann funktionieren Vergleiche nicht mehr wie erwartet, da nicht die Zahlen verglichen werden, sondern ihre Darstellungen als Zeichenfolgen:

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

Ich fand dann heraus, dass ich das Problem tatsächlich lösen kann, indem ich ->eine Besetzung verwende, um int:

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

Das funktioniert, aber es ist nicht so schön, dass ich den tatsächlichen Typ kennen muss (der Typ ageim JSON-Dokument ist numbersowieso. Warum kann PostgreSQL das nicht selbst herausfinden?).

Ich habe dann herausgefunden, dass, wenn ich manuell in textdie ::Syntax konvertiere , auch alles wie erwartet funktioniert - obwohl wir jetzt wieder Zeichenfolgen vergleichen.

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

Wenn ich das dann mit dem Namen anstelle des Alters versuche, funktioniert es nicht:

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

Dies führt zu einem Fehler:

ungültige Eingabesyntax für Typ json

Ganz offensichtlich bekomme ich hier nichts. Leider ist es ziemlich schwierig, Beispiele für die Verwendung von JSON mit PostgreSQL in der Praxis zu finden.

Irgendwelche Hinweise?

Golo Roden
quelle
1
In data->'name'::textwandeln Sie die 'name'Zeichenfolge in Text um, nicht in das Ergebnis. Beim Vergleich mit wird kein Fehler angezeigt, '25'da 25es sich um ein gültiges JSON-Literal handelt. ist aber Jennynicht (obwohl "Jenny"wäre).
Chirlu
Danke, das ist die Lösung :-). Ich habe verwechselt 'Jenny'mit '"Jenny"'.
Golo Roden

Antworten:

13

Dies funktioniert nicht, weil versucht wird, einen jsonbWert auf zu setzen integer.

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

Das würde tatsächlich funktionieren:

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

Oder kürzer:

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

Und das:

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

Scheint wie Verwechslung mit den beiden Operatoren ->und->> und Operator Vorrang . Die Besetzung ::bindet stärker als die json (b) -Operatoren.

Typ dynamisch herausfinden

Dies ist der interessantere Teil Ihrer Frage:

Die Art des Alters im JSON-Dokument ist sowieso die Zahl. Warum kann PostgreSQL das nicht selbst herausfinden?

SQL ist eine streng typisierte Sprache, bei der nicht derselbe Ausdruck integerin einer Zeile und textin der nächsten ausgewertet werden kann . Da Sie jedoch nur booleanam Testergebnis interessiert sind , können Sie diese Einschränkung mit einem CASEAusdruck umgehen, der sich abhängig vom Ergebnis von 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;

Ein untypisiertes Zeichenfolgenliteral rechts vom >Operator wird automatisch zum jeweiligen Typ des Werts links gezwungen. Wenn Sie dort einen typisierten Wert eingeben, muss der Typ übereinstimmen oder explizit umgewandelt werden - es sei denn, im System ist eine angemessene implizite Umwandlung registriert.

Wenn Sie wissen, dass alle numerischen Werte tatsächlich sind integer, können Sie auch:

... (data->>'age')::int > 25 ...
Erwin Brandstetter
quelle
Was ist der sqlalchemy-Kernausdruck für den obigen Vergleich der select-Anweisung, z. s = select ([Issues]). where (Issues.c.id == mid) .select_from (Issues, ..... Outerjoin (Issues.c.data ['type_id'] == mtypes.c.id) ) ... Hier gibt.c.data jsonb Datentyp aus und wird mit mtypes.c.id vom Integer-Typ verglichen
user956424