Comment supprimer des éléments connus d'un tableau JSON [] dans PostgreSQL?

8

Je suis confronté à un problème concernant l'utilisation du type de données JSON dans PostgreSQL. J'essaie de réaliser le stockage d'un modèle Java dénormalisé dans la base de données. Le modèle comporte des listes d'objets complexes. J'ai donc décidé de les modéliser en JSON dans des tableaux natifs PostgreSQL.

Voici un extrait de ma déclaration de création de table:

CREATE TABLE test.persons
(
  id UUID,
  firstName TEXT,
  lastName TEXT,
  communicationData JSON[],
  CONSTRAINT pk_person PRIMARY KEY (id)
);

Comme vous pouvez le voir, il s'agit d'une personne présentant une liste d'objets de données de communication en JSON. L'un de ces objets pourrait ressembler à ceci:

{"value" : "03334/254147", "typeId" : "ea4e7d7e-7b87-4628-ba50-6a5f6e63dbf6"}

Je peux facilement ajouter un tel objet JSON à un tableau en utilisant array_append de PostgreSQL. Cependant, je ne parviens pas à supprimer une valeur connue du tableau. Considérez par exemple cette instruction SQL:

UPDATE test.persons
SET communicationData = array_remove(
      communicationData, 
      '{"value" : "03334/254147", "typeId" : "ea4e7d7e-7b87-4628-ba50-6a5f6e63dbf6"}'::JSON
    )
WHERE id = 'f671eb6a-d603-11e3-bf6f-07ba007d953d';

Cela échoue avec ERROR: could not identify an equality operator for type json. Avez-vous un indice sur la façon de supprimer une valeur connue du tableau JSON? Il serait également possible de supprimer par position dans le tableau, car je sais que l'on aussi ...

La version de PostgreSQL est 9.3.4.

spa
la source

Réponses:

11

jsonb dans Postgres 9.4 ou version ultérieure

Considérez le jsonbtype de données dans Postgres 9.4 - «b» pour «binaire». Entre autres, il existe un opérateur d'égalité =pourjsonb . La plupart des gens voudront changer.

Blog Depesz sur jsonb.

json

Aucun =opérateur n'est défini pour le type de données json, car il n'existe pas de méthode bien définie pour établir l'égalité pour les jsonvaleurs entières . Mais voyez ci-dessous.

Vous pouvez convertir textet utiliser l' =opérateur. Ceci est court, mais ne fonctionne que si votre représentation textuelle correspond. Intrinsèquement peu fiable, à l'exception des cas d'angle. Voir:

Ou vous pouvez unnestutiliser le tableau et utiliser l' ->>opérateur pour .. get JSON object field as textet comparer des champs individuels.

Table de test

2 rangées: la première comme dans la question, la seconde avec des valeurs simples.

CREATE TABLE tbl (
   tbl_id int PRIMARY KEY
 , jar    json[]
);

INSERT INTO t VALUES
   (1, '{"{\"value\" : \"03334/254146\", \"typeId\" : \"ea4e7d7e-7b87-4628-ba50-f5\"}"
        ,"{\"value\" : \"03334/254147\", \"typeId\" : \"ea4e7d7e-7b87-4628-ba50-f6\"}"
        ,"{\"value\" : \"03334/254148\", \"typeId\" : \"ea4e7d7e-7b87-4628-ba50-f7\"}"}')

 , (2, '{"{\"value\" : \"a\", \"typeId\" : \"x\"}"
        ,"{\"value\" : \"b\", \"typeId\" : \"y\"}"
        ,"{\"value\" : \"c\", \"typeId\" : \"z\"}"}');

Démos

Démo 1: vous pouvez utiliser array_remove()avec des textreprésentations (peu fiable).

SELECT tbl_id
     , jar, array_length(jar, 1) AS jar_len
     , jar::text[] AS t, array_length(jar::text[], 1) AS t_len
     , array_remove(jar::text[], '{"value" : "03334/254147", "typeId" : "ea4e7d7e-7b87-4628-ba50-f6"}'::text) AS t_result
     , array_remove(jar::text[], '{"value" : "03334/254147", "typeId" : "ea4e7d7e-7b87-4628-ba50-f6"}'::text)::json[] AS j_result
FROM   tbl;

Démo 2: dégroupez le tableau et testez les champs des éléments individuels.

SELECT tbl_id, array_agg(j) AS j_new
FROM   tbl, unnest(jar) AS j   -- LATERAL JOIN
WHERE  j->>'value' <> '03334/254146'
AND    j->>'typeId' <> 'ea4e7d7e-7b87-4628-ba50-6a5f6e63dbf5'
GROUP  BY 1;

Démo 3: test alternatif avec type de ligne.

SELECT tbl_id, array_agg(j) AS j_new
FROM   tbl, unnest(jar) AS j   -- LATERAL JOIN
WHERE  (j->>'value', j->>'typeId') NOT IN (
         ('03334/254146', 'ea4e7d7e-7b87-4628-ba50-6a5f6e63dbf5')
        ,('a', 'x')
       )
GROUP  BY 1;

UPDATE comme demandé

Enfin, voici comment mettre en œuvre votre UPDATE:

UPDATE tbl t
SET    jar = j.jar
FROM   tbl t1
CROSS  JOIN LATERAL (
   SELECT ARRAY(
      SELECT j
      FROM   unnest(t1.jar) AS j  -- LATERAL JOIN
      WHERE  j->>'value'  <> 'a'
      AND    j->>'typeId' <> 'x'
      ) AS jar
   ) j
WHERE  t1.tbl_id = 2              -- only relevant rows
AND    t1.tbl_id = t.tbl_id;

db <> violon ici

À propos de l'implicite LATERAL JOIN:

À propos des tableaux imbriqués:

Conception DB

Pour simplifier votre situation, envisagez un schéma normalisé : une table distincte pour les jsonvaleurs (au lieu de la colonne du tableau), jointe dans une relation: 1 à la table principale.

Erwin Brandstetter
la source
Il fonctionne comme un charme. Oui, ce serait plus facile avec des données normalisées, mais je suis dans un scénario de lecture à 98% et d'écriture à 2%. Je voulais donc expérimenter la dénormalisation :-) Y a-t-il quelque chose de prévu pour Postgres 9.4 qui pourrait aider avec la question d'origine?
spa
@spa: En fait, Postgres 9.4 apportera jsonb. Je suppose que vous allez l'adorer. Ajout d'un chapitre avec des liens.
Erwin Brandstetter
C'est vraiment cool. Merci pour l'information.
spa