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.

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.


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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.