Como remover elementos conhecidos de uma matriz JSON [] no PostgreSQL?

8

Estou com um problema relacionado ao uso do tipo de dados JSON no PostgreSQL. Eu tento conseguir armazenar um modelo Java desnormalizado no banco de dados. O modelo apresenta listas de objetos complexos. Assim, decidi modelá-los como JSON em matrizes nativas do PostgreSQL.

Este é um trecho simplificado da minha declaração de criação de tabela:

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

Como você pode ver, é uma pessoa que apresenta uma lista de objetos de dados de comunicação em JSON. Um desses objetos pode ficar assim:

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

Posso anexar facilmente um objeto JSON a uma matriz usando o array_append do PostgreSQL. No entanto, falhei ao remover um valor conhecido da matriz. Considere esta instrução 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';

Isso falha com ERROR: could not identify an equality operator for type json. Você tem uma dica de como eu poderia remover um valor conhecido da matriz JSON? Também seria possível remover por posição na matriz, como eu sei que um também ...

A versão do PostgreSQL é 9.3.4.

spa
fonte

Respostas:

11

jsonb no Postgres 9.4 ou posterior

Considere o jsonbtipo de dados no Postgres 9.4 - 'b' para 'binário'. Entre outras coisas, existe um operador de igualdade =parajsonb . A maioria das pessoas vai querer mudar.

Blog do Depesz sobre jsonb.

json

Não há =operador definido para o tipo de dados json, porque não existe um método bem definido para estabelecer igualdade para jsonvalores inteiros . Mas veja abaixo.

Você pode transmitir texte usar o =operador. Isso é curto, mas só funciona se sua representação de texto coincidir. Inerentemente não confiável, exceto nos casos de canto. Vejo:

Ou você pode fazer unnesta matriz e usar o ->>operador para .. get JSON object field as texte comparar campos individuais.

Tabela de teste

2 linhas: a primeira como na pergunta, a segunda com 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\"}"}');

Demonstrações

Demo 1: você pode usar array_remove()com textrepresentações (não confiável).

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: anule a matriz e teste os campos de elementos individuais.

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: teste alternativo com o tipo de linha.

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 como pedido

Por fim, é assim que você pode implementar o seu 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 <> mexer aqui

Sobre o implícito LATERAL JOIN:

Sobre matrizes desinteressantes:

Design do banco de dados

Para simplificar sua situação, considere um esquema normalizado : uma tabela separada para os jsonvalores (em vez da coluna da matriz), unida em um relacionamento: 1 à tabela principal.

Erwin Brandstetter
fonte
Ele funciona como um encanto. Sim, seria mais fácil com dados normalizados, mas estou em um cenário de 98% de leitura e 2% de gravação. Então, eu queria experimentar a desnormalização :-) Existe algo planejado para o Postgres 9.4 que possa ajudar com a pergunta original?
spa
@spa: Na verdade, o Postgres 9.4 trará jsonb. Espero que você ame. Adicionado um capítulo com links.
Erwin Brandstetter
Isso é muito legal. Obrigado pela atenção.
spa