Wie bekomme ich ein bestimmtes Objekt aus dem jsonb-Array in PostgreSQL?

15

Ich habe ein Feld namens "Benutzer", das ein JSON-Array enthält, das ungefähr so ​​aussieht:

"user"::

[{ "_id" : "1", "count" : "4" }, { "_id" : "3", "count": "4"}]

Jetzt möchte ich eine Abfrage wie:

select count from tablename where id = "1"

Ich kann das bestimmte Feld nicht countaus einem Array von JSON-Objekten in PostgreSQL 9.4 abrufen.

Rabi C Shah
quelle

Antworten:

17

Es wäre viel effizienter, Ihre Werte in einem normalisierten Schema zu speichern. Sie können es jedoch auch mit Ihrem aktuellen Setup zum Laufen bringen.

Annahmen

Angenommen, diese Tabellendefinition:

CREATE TABLE tbl (tbl_id int, usr jsonb);

"Benutzer" ist ein reserviertes Wort und würde doppelte Anführungszeichen erfordern, um als Spaltenname verwendet zu werden. Tu das nicht. Ich benutze usrstattdessen.

Abfrage

Die Abfrage ist nicht so trivial wie die (jetzt gelöschten) Kommentare es scheinen ließen:

SELECT t.tbl_id, obj.val->>'count' AS count
FROM   tbl t
JOIN   LATERAL jsonb_array_elements(t.usr) obj(val) ON obj.val->>'_id' = '1'
WHERE  t.usr @> '[{"_id":"1"}]';

Es gibt 3 grundlegende Schritte :

1. Identifizieren Sie qualifizierende Zeilen kostengünstig

WHERE t.usr @> '[{"_id":"1"}]'Identifiziert Zeilen mit übereinstimmendem Objekt im JSON-Array. Der Ausdruck kann einen generischen GIN-Index für die jsonbSpalte oder einen mit der spezielleren Operatorklasse verwenden jsonb_path_ops:

CREATE INDEX tbl_usr_gin_idx ON tbl USING gin (usr jsonb_path_ops);

Die hinzugefügte WHEREKlausel ist logisch redundant , es ist jedoch erforderlich, den Index zu verwenden. Der Ausdruck in der Join-Klausel erzwingt dieselbe Bedingung, jedoch erst, nachdem das Array in jeder bisher qualifizierenden Zeile nicht getestet wurde. Mit der Indexunterstützung verarbeitet Postgres zunächst nur Zeilen, die ein qualifizierendes Objekt enthalten. Bei kleinen Tabellen spielt das keine große Rolle, bei großen Tabellen und nur wenigen qualifizierenden Zeilen macht dies einen großen Unterschied.

Verbunden:

2. Identifizieren Sie übereinstimmende Objekte im Array

Unnest mit jsonb_array_elements(). ( unnest()ist nur für Postgres-Array-Typen geeignet.) Da wir nur daran interessiert sind, Objekte tatsächlich abzugleichen, filtern Sie sofort in der Verknüpfungsbedingung.

Verbunden:

3. Extrahieren Sie den Wert für den verschachtelten Schlüssel 'count'

Nachdem qualifizierende Objekte extrahiert wurden, einfach : obj.val->>'count'.

Erwin Brandstetter
quelle
2
Woher kommt das obj(value)? Ist es auf der LATERAL JOIN, der jsonb_array_elementsoder woanders?
Tyler DeWitt
Es sieht so aus, als wäre die Formatierung durcheinander geraten. Lese ich das richtig JOIN LATERAL jsonb_array_elements(t.usr) obj(value) is short for JOIN LATERAL jsonb_array_elements(t.usr) AS obj(value)und das obj(value)ist ein Tabellen- und Spaltenalias? Wenn objes sich in diesem Beispiel um einen Tabellenalias handelt, wofür handelt es sich um einen Alias? Das Set kehrte von zurück jsonb_array_elements?
Tyler DeWitt
1
ja und ja. Ich habe meinen verschlüsselten Kommentar entfernt.
Erwin Brandstetter
Muss der Spaltenalias verwendet werden? In meinen Tests JOIN LATERAL jsonb_array_elements(t.usr) obj ON obj->>'_id' = '1'hatte der gleiche Effekt (sobald Sie die select-Anweisung aktualisieren, um valuestatt zu verwenden val). Es scheint, dass jsonb_array_elements(t.usr)eine Tabelle mit nur einer Spalte zurückgegeben wird. Ist Postgres klug und erkennt, dass dies obj ->>dasselbe ist wie obj.val ->>?
Tyler DeWitt
Mit nur einer einzigen Spalte verwendet Postgres einen bestimmten Alias ​​als Tabellen- und Spaltennamen. Ich bin nur explizit, da es viele Set-Return-Funktionen gibt, die mehr als eine Spalte zurückgeben.
Erwin Brandstetter