web-dev-qa-db-de.com

Postgresql-Abfrage-Array von Objekten im JSONB-Feld

Ich habe eine Tabelle in einer postgresql 9.4-Datenbank mit einem jsonb-Feld namens receivers. Einige Beispielzeilen:

[{"id": "145119603", "name": "145119603", "type": 2}]
[{"id": "1884595530", "name": "1884595530", "type": 1}]
[{"id": "363058213", "name": "363058213", "type": 1}]
[{"id": "1427965764", "name": "1427965764", "type": 1}]
[{"id": "193623800", "name": "193623800", "type": 0}, {"id": "419955814", "name": "419955814", "type": 0}]
[{"id": "624635532", "name": "624635532", "type": 0}, {"id": "1884595530", "name": "1884595530", "type": 1}]
[{"id": "791712670", "name": "791712670", "type": 0}]
[{"id": "895207852", "name": "895207852", "type": 0}]
[{"id": "144695994", "name": "144695994", "type": 0}, {"id": "384217055", "name": "384217055", "type": 0}]
[{"id": "1079725696", "name": "1079725696", "type": 0}]

Ich habe eine Liste von Werten für id und möchte eine beliebige Zeile auswählen, die ein Objekt mit einem der Werte aus dieser Liste innerhalb des Arrays im Feld jsonb enthält.

Ist das möglich? Gibt es einen GIN-Index, mit dem ich das beschleunigen kann?

31
user3761100

Es gibt keine einzelne Operation, die Ihnen helfen kann, aber Sie haben ein paar Möglichkeiten:

1. Wenn Sie eine kleine (und feste) Anzahl von IDs abfragen möchten, können Sie mehrere Containment-Operatoren @> In Kombination mit or; zB:

where data @> '[{"id": "1884595530"}]' or data @> '[{"id": "791712670"}]'

Ein einfacher gin -Index kann Ihnen hier bei Ihrer Datenspalte helfen.

2. Wenn Sie eine variable Anzahl von IDs haben (oder Sie haben viele davon), können Sie json[b]_array_elements() verwenden, um jede zu extrahieren Element des Arrays, erstellen Sie eine ID-Liste und fragen Sie sie dann mit dem Operator any-containment ab ?|:

select *
from   jsonbtest
where  to_json(array(select jsonb_array_elements(data) ->> 'id'))::jsonb ?|
         array['1884595530', '791712670'];

Leider können Sie einen Ausdruck, der eine Unterabfrage enthält, nicht indizieren. Wenn Sie es indizieren möchten, müssen Sie eine Funktion dafür erstellen:

create function idlist_jsonb(jsonbtest)
  returns jsonb
  language sql
  strict
  immutable
as $func$
  select to_json(array(select jsonb_array_elements($1.data) ->> 'id'))::jsonb
$func$;

create index on jsonbtest using gin (idlist_jsonb(jsonbtest));

Danach können Sie IDs wie folgt abfragen:

select *, jsonbtest.idlist_jsonb
from   jsonbtest
where  jsonbtest.idlist_jsonb ?| array['193623800', '895207852'];

Hinweis: Ich habe Punktnotation/berechnetes Feld hier verwendet, aber das musst du nicht.

3. Aber an diesem Punkt müssen Sie sich nicht an json [b] halten: Sie haben ein einfaches Textarray, das von PostgreSQL unterstützt wird auch.

create function idlist_array(jsonbtest)
  returns text[]
  language sql
  strict
  immutable
as $func$
  select array(select jsonb_array_elements($1.data) ->> 'id')
$func$;

create index on jsonbtest using gin (idlist_array(jsonbtest));

Fragen Sie dieses berechnete Feld mit dem Überlappungs-Array-Operator && Ab:

select *, jsonbtest.idlist_array
from   jsonbtest
where  jsonbtest.idlist_array && array['193623800', '895207852'];

Anmerkung: Aus meinen internen Tests geht hervor, dass diese letztere Lösung mit höheren Kosten als die jsonb-Variante berechnet wird, aber tatsächlich ist sie ein wenig schneller. Wenn Ihnen die Leistung wirklich wichtig ist, sollten Sie beide testen.

48
pozs

Ich finde eine Problemumgehung:
where data::text similar to '%("id": "145119603"|"id": "1884595530")%'

4
taiberium