¿Cómo eliminar elementos conocidos de una matriz JSON [] en PostgreSQL?


8

Estoy enfrentando un problema con respecto al uso del tipo de datos JSON en PostgreSQL. Intento lograr almacenar un modelo Java desnormalizado en la base de datos. El modelo presenta listas de objetos complejos. Por lo tanto, decidí modelarlos como JSON en matrices PostgreSQL nativas.

Este es un fragmento despojado de mi declaración de creación de tabla:

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

Como puede ver, es una persona que presenta una lista de objetos de datos de comunicación en JSON. Uno de esos objetos podría verse así:

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

Puedo agregar fácilmente un objeto JSON a una matriz usando el array_append de PostgreSQL. Sin embargo, no puedo eliminar un valor conocido de la matriz. Considere fe esta declaración 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';

Esto falla con ERROR: could not identify an equality operator for type json. ¿Tiene alguna pista de cómo podría eliminar un valor conocido de la matriz JSON? También sería posible eliminar por posición en la matriz, ya que sé que también ...

La versión de PostgreSQL es 9.3.4.

Respuestas:


11

jsonb en Postgres 9.4 o posterior

Considere el jsonbtipo de datos en Postgres 9.4 - 'b' para 'binario'. Entre otras cosas, hay un operador de igualdad =parajsonb . La mayoría de la gente querrá cambiar.

Blog de Depesz sobre jsonb.

json

No hay un =operador definido para el tipo de datos json, porque no hay un método bien definido para establecer la igualdad de los jsonvalores completos . Pero mira abajo.

Usted podría emitir a texty luego usar el =operador. Esto es breve, pero solo funciona si su representación de texto coincide. Inherentemente poco confiable, excepto en casos de esquina. Ver:

O puede unnestla matriz y usar el ->>operador para ... get JSON object field as texty comparar campos individuales.

Mesa de prueba

2 filas: la primera como en la pregunta, la segunda con valores 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\"}"}');

Población

Demostración 1: se puede usar array_remove()con textrepresentaciones (poco confiable).

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;

Demostración 2: anule la matriz y los campos de prueba de elementos individuales.

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: prueba alternativa con tipo de fila.

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 de acuerdo a lo pedido

Finalmente, así es como podría implementar su 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 <> violín aquí

Sobre lo implícito LATERAL JOIN:

Acerca de la anulación de matrices:

Diseño DB

Para simplificar su situación, considere un esquema normalizado : una tabla separada para los jsonvalores (en lugar de la columna de matriz), unida en una relación: 1 con la tabla principal.


Funciona a las mil maravillas. Sí, sería más fácil con datos normalizados, pero estoy en un escenario de 98% de lectura y 2% de escritura. Así que quería experimentar con la desnormalización :-) ¿Hay algo relanzado planeado para Postgres 9.4 que pueda ayudar con la pregunta original?
spa

@spa: En realidad, Postgres 9.4 traerá jsonb. Espero que te encante. Se agregó un capítulo con enlaces.
Erwin Brandstetter

Eso es realmente genial. Gracias por el aviso.
spa
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.