Как удалить известные элементы из массива JSON [] в PostgreSQL?

8

У меня проблема с использованием типа данных JSON в PostgreSQL. Я пытаюсь добиться сохранения модели Java, денормализованной в БД. Модель имеет списки сложных объектов. Поэтому я решил смоделировать их как JSON в собственных массивах PostgreSQL.

Это урезанный фрагмент моего оператора создания таблицы:

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

Как видите, это человек, представляющий список объектов коммуникационных данных в JSON. Один из таких объектов может выглядеть так:

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

Я легко могу добавить такой объект JSON в массив, используя array_append в PostgreSQL. Однако мне не удается удалить известное значение из массива. Рассмотрим этот 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';

Это терпит неудачу с ERROR: could not identify an equality operator for type json. У вас есть подсказка, как я могу удалить известное значение из массива JSON? Также было бы возможно удалить по позиции в массиве, так как я знаю, что один также ...

Версия PostgreSQL - 9.3.4.

спа
источник

Ответы:

11

jsonb в Postgres 9.4 или позже

Рассмотрим jsonbтип данных в Postgres 9.4 - «b» для «двоичного». Помимо прочего, есть оператор равенства =дляjsonb . Большинство людей захотят переключиться.

Блог Depesz о JSONB.

json

Для =типа данных не определен оператор json, поскольку нет четко определенного метода для установления равенства для целых jsonзначений. Но смотри ниже.

Вы можете привести к textи затем использовать =оператор. Это коротко, но работает, только если ваше текстовое представление совпадает. По своей сути ненадежны, за исключением угловых случаев. Видеть:

Или вы можете unnestмассив и использовать ->>оператор, чтобы .. get JSON object field as textи сравнить отдельные поля.

Тестовый стол

2 строки: первая, как в вопросе, вторая с простыми значениями.

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

демос

Демонстрация 1: вы можете использовать array_remove()с textпредставлениями (ненадежными).

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;

Демонстрация 2: раскрутить массив и проверить поля отдельных элементов.

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;

Демонстрация 3: альтернативный тест с типом строки.

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 как просили

Наконец, вот как вы можете реализовать 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;

дБ <> скрипка здесь

О неявном LATERAL JOIN:

О несмешивающихся массивах:

Дизайн БД

Чтобы упростить ситуацию, рассмотрим нормализованную схему : отдельную таблицу для jsonзначений (вместо столбца массива), объединенную в отношении: 1 к основной таблице.

Эрвин Брандштеттер
источник
Работает как часы. Да, было бы проще с нормализованными данными, но я нахожусь в сценарии чтения 98% и записи 2%. Поэтому я хотел поэкспериментировать с денормализацией :-) Планируется ли что-нибудь выпущенное для Postgres 9.4, что может помочь с первоначальным вопросом?
Спа
@spa: На самом деле, Postgres 9.4 принесет jsonb. Я ожидаю, что вам понравится. Добавлена ​​глава со ссылками.
Эрвин Брандштеттер,
Это действительно круто. Спасибо за внимание.
Спа