Wie entferne ich bekannte Elemente aus einem JSON [] -Array in PostgreSQL?

8

Ich habe ein Problem mit der Verwendung des JSON-Datentyps in PostgreSQL. Ich versuche, ein in der DB denormalisiertes Java-Modell zu speichern. Das Modell enthält Listen komplexer Objekte. Daher habe ich beschlossen, diese als JSON in nativen PostgreSQL-Arrays zu modellieren.

Dies ist ein abgespeckter Ausschnitt meiner Anweisung zur Tabellenerstellung:

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

Wie Sie sehen können, handelt es sich um eine Person mit einer Liste von Kommunikationsdatenobjekten in JSON. Eines dieser Objekte könnte folgendermaßen aussehen:

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

Ich kann ein solches JSON-Objekt einfach mit array_append von PostgreSQL an ein Array anhängen. Ich kann jedoch keinen bekannten Wert aus dem Array entfernen. Betrachten Sie zB diese SQL-Anweisung:

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

Dies schlägt fehl mit ERROR: could not identify an equality operator for type json . Haben Sie einen Hinweis, wie ich einen bekannten Wert aus dem JSON-Array entfernen kann? Es wäre auch möglich, nach Position im Array zu entfernen, da ich weiß, dass man auch ...

Die PostgreSQL-Version ist 9.3.4.

Spa
quelle

Antworten:

11

jsonb in Postgres 9.4 oder höher

Betrachten Sie den jsonbDatentyp in Postgres 9.4 - 'b' für 'binär'. Unter anderem gibt es einen Gleichheitsoperator =fürjsonb . Die meisten Leute werden wechseln wollen.

Depesz Blog über jsonb.

json

=Für den Datentyp ist kein Operator definiert json, da es keine genau definierte Methode gibt, um die Gleichheit für ganze jsonWerte herzustellen . Aber siehe unten.

Sie könnten werfen textund dann die Verwendung =Operator. Dies ist kurz, funktioniert aber nur, wenn Ihre Textdarstellung übereinstimmt. Inhärent unzuverlässig, außer in Eckfällen. Sehen:

Oder Sie können unnestdas Array und den ->>Operator verwenden , get JSON object field as textum einzelne Felder zu vergleichen.

Testtabelle

2 Zeilen: erste wie in der Frage, zweite mit einfachen Werten.

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: Sie konnten verwenden array_remove()mit textDarstellungen (unzuverlässig).

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: Unnest die Array- und Testfelder einzelner Elemente.

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: Alternativer Test mit Zeilentyp.

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 wie gewünscht

Schließlich können Sie Folgendes implementieren 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 <> hier fummeln

Über das Implizite LATERAL JOIN:

Über unnesting Arrays:

DB Design

Um Ihre Situation zu vereinfachen, betrachten Sie ein normalisiertes Schema : eine separate Tabelle für die jsonWerte (anstelle der Array-Spalte), die in einer: 1-Beziehung zur Haupttabelle verknüpft ist.

Erwin Brandstetter
quelle
Es wirkt wie ein Zauber. Ja, mit normalisierten Daten wäre es einfacher, aber ich bin in einem Szenario mit 98% Lesen und 2% Schreiben. Also wollte ich mit Denormalisierung experimentieren :-) Ist für Postgres 9.4 etwas geplant, das bei der ursprünglichen Frage helfen könnte?
Spa
@spa: Eigentlich wird Postgres 9.4 bringen jsonb. Ich gehe davon aus, dass du es lieben wirst. Ein Kapitel mit Links wurde hinzugefügt.
Erwin Brandstetter
Das ist echt cool. Danke für die Warnung.
Spa