Come rimuovere elementi noti da un array JSON [] in PostgreSQL?


8

Sto riscontrando un problema relativo all'uso del tipo di dati JSON in PostgreSQL. Cerco di ottenere la memorizzazione di un modello Java denormalizzato nel DB. Il modello presenta elenchi di oggetti complessi. Così, ho deciso di modellarli come JSON in array PostgreSQL nativi.

Questo è uno snippet ridotto della mia dichiarazione di creazione della tabella:

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

Come puoi vedere, è una persona che presenta un elenco di oggetti dati di comunicazione in JSON. Uno di questi oggetti potrebbe apparire così:

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

Posso facilmente aggiungere un tale oggetto JSON a un array usando array_append di PostgreSQL. Tuttavia, non riesco a rimuovere un valore noto dall'array. Prendi in considerazione questa istruzione 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';

Questo fallisce con ERROR: could not identify an equality operator for type json. Hai un suggerimento su come potrei rimuovere un valore noto dall'array JSON? Sarebbe anche possibile rimuovere per posizione nella matrice, poiché so che anche uno ...

La versione PostgreSQL è 9.3.4.

Risposte:


11

jsonb in Postgres 9.4 o successivo

Considera il jsonbtipo di dati in Postgres 9.4 - 'b' per 'binario'. Tra le altre cose, esiste un operatore =perjsonb l' uguaglianza . Molte persone vorranno cambiare.

Blog Depesz su jsonb.

json

Non esiste un =operatore definito per il tipo di dati json, poiché non esiste un metodo ben definito per stabilire l'uguaglianza per jsonvalori interi . Ma vedi sotto.

È possibile eseguire il cast texte quindi utilizzare l' =operatore. Questo è breve, ma funziona solo se la rappresentazione del testo corrisponde. Inerentemente inaffidabile, ad eccezione dei casi angolari. Vedere:

Oppure puoi unnestusare l'array e usare l' ->>operatore per ... get JSON object field as texte confrontare i singoli campi.

Tabella di prova

2 righe: la prima come nella domanda, la seconda con valori semplici.

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\"}"}');

Demos

Demo 1: è possibile utilizzare array_remove()con le textrappresentazioni (inaffidabili).

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;

Demo 2: annulla l'array e verifica i campi dei singoli elementi.

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;

Demo 3: test alternativo con tipo di riga.

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 come richiesto

Infine, ecco come potresti implementare 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 <> violino qui

Circa l'implicito LATERAL JOIN:

A proposito di array inutili:

DB design

Per semplificare la tua situazione, considera uno schema normalizzato : una tabella separata per i jsonvalori (anziché la colonna dell'array), unita in una relazione: 1 con la tabella principale.


Esso funziona magicamente. Sì, sarebbe più facile con i dati normalizzati, ma sono in uno scenario di lettura del 98%, scrittura del 2%. Quindi volevo sperimentare la denormalizzazione :-) C'è qualcosa in programma per Postgres 9.4 che potrebbe aiutare con la domanda originale?
spa

@spa: In realtà, Postgres 9.4 porterà jsonb. Mi aspetto che ti piacerà. Aggiunto un capitolo con collegamenti.
Erwin Brandstetter,

È davvero fantastico. Grazie per il testa a testa.
spa
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.