web-dev-qa-db-fra.com

Tableau de requêtes PostgreSQL d'objets dans le champ JSONB

J'ai une table dans une base de données postgresql 9.4 avec un champ jsonb appelé récepteurs. Quelques exemples de lignes:

[{"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}]

J'ai une liste de valeurs pour id et je souhaite sélectionner n'importe quelle ligne contenant un objet avec l'une des valeurs de cette liste, dans le tableau du champ jsonb.

Est-ce possible? Y a-t-il un indice GIN que je peux faire pour accélérer cela?

31
user3761100

Il n'y a pas d'opération unique, ce qui peut vous aider, mais vous avez quelques options:

1. Si vous avez un petit nombre (et fixe) d'ID à interroger, vous pouvez utiliser plusieurs opérateurs de confinement @> Combinés avec or; f.ex .:

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

Un simple index gin peut vous aider sur votre colonne de données ici.

2. Si vous avez un nombre variable d'ID (ou si vous en avez beaucoup), vous pouvez utiliser json[b]_array_elements() pour extraire chaque élément du tableau, créez une liste d'id, puis interrogez-la avec l'opérateur any-containment ?|:

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

Malheureusement, vous ne pouvez pas indexer une expression contenant une sous-requête. Si vous souhaitez l'indexer, vous devez lui créer une fonction:

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));

Après cela, vous pouvez interroger des ID comme celui-ci:

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

Remarque: J'ai utilisé notation par points/champ calculé ici, mais ce n'est pas obligatoire.

3. Mais à ce stade, vous n'avez pas à vous en tenir à json [b]: vous avez un simple tableau de texte, qui est pris en charge par PostgreSQL aussi.

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));

Et interrogez ce champ calculé avec l'opérateur de tableau de chevauchement &&:

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

Remarque: D'après mes tests internes, cette dernière solution est calculée avec un coût plus élevé que la variante jsonb, mais en fait elle est plus rapide que cela, un peu. Si les performances comptent vraiment pour vous, vous devez tester les deux.

48
pozs

Je trouve une solution:
where data::text similar to '%("id": "145119603"|"id": "1884595530")%'

4
taiberium