Удалить элемент numeri c из массива JsonB - PullRequest
3 голосов
/ 07 мая 2020

У меня есть jsonb значение с вложенным массивом JSON, и мне нужно удалить элемент:

{"values": ["11", "22", "33"]}

jsonb_set(column_name, '{values}', ((column_name -> 'values') - '33')) -- WORKS!

У меня также есть аналогичное jsonb значение с числами , а не строки:

{"values": [11, 22, 33]}

jsonb_set(column_name, '{values}', ((column_name -> 'values') - 33))  -- FAILS! 

В этом случае 33 используется как индекс массива.

Как удалить элементы из массива JSON, если эти элементы являются числами?

Ответы [ 2 ]

2 голосов
/ 08 мая 2020

Два утверждения:

  1. Многие Postgres JSON функции и операторы нацелены на ключ в парах ключ / значение. Строки ("abc" или "33") в массивах JSON обрабатываются как ключи без значения. Но элементы массива numeri c (33 или 123.45) обрабатываются как values ​​.

  2. В настоящее время существует три варианта - оператор. Два из них применимы здесь. Как описано в недавно разъясненном руководстве (current / devel):

    Оператор
    Описание
    Пример (ы)
    : ------- --------------
    jsonb - textjsonb
    Удаляет ключ (и его значение) из объекта JSON или соответствует строковое значение (я) из массива JSON.
    '{"a": "b", "c": "d"}'::jsonb - 'a'{"c": "d"}
    '["a", "b", "c", "b"]'::jsonb - 'b'["a", "c"]
    ...
    jsonb - integerjsonb
    Удаляет элемент массива с указанным индексом (отрицательные целые числа считаются с конца).
    Выдает ошибку, если JSON значение не является массивом.
    '["a", "b"]'::jsonb - 1["a"]

Правым операндом является numeri c literal , Postgres разрешение типа оператора прибывает в более поздний вариант.

К сожалению, мы не можем использовать первый вариант для начала из-за утверждения 1.

Таким образом, мы должны использовать обходной путь например:

SELECT jsonb_set(column_name
               , '{values}'
               , (SELECT jsonb_agg(val)
                  FROM   jsonb_array_elements(t.column_name -> 'values') x(val)
                  WHERE  val <> jsonb '33')
                 ) AS column_name
FROM   tbl t;

db <> fiddle здесь - с расширенным тестовым примером

Do * 1 077 * not приводит не вложенные элементы к integer (как предлагает другой ответ).

  • Значения Numeri c могут не соответствовать integer.
  • JSON массивы (в отличие от массивов Postgres) могут содержать элементы разных типов. Таким образом, некоторые элементы массива могут быть numeric, а другие string и т. Д. c.
  • Приведение всех элементов массива (слева) дороже. Просто введите значение для замены (справа).

Таким образом, это работает для любых типов, а не только для целых (JSON numeri c). Пример:

'{"values": ["abc", "22", 33]}') 
2 голосов
/ 08 мая 2020

К сожалению, Postgres json оператор - поддерживает только строковые значения, как объяснено в документации :

операнд: -

тип правого операнда: text

описание: Удалить пару ключ / значение или строковый элемент из левого операнда. Пары ключ / значение сопоставляются на основе их значения ключа.

С другой стороны, если вы передаете целое значение в качестве правого операнда, Postgres считает его индексом элемент массива, который необходимо удалить.

Альтернативный вариант - разложить массив с помощью jsonb_array_elements() и бокового соединения, отфильтровать нежелательное значение, а затем повторно агрегировать:

select jsonb_set(column_name, '{values}', new_values) new_column_name
from mytable t
left join lateral (
    select jsonb_agg(val) new_values
    from jsonb_array_elements(t.column_name -> 'values') x(val)
    where val::int <> 33
) x on 1 = 1

Демонстрация на DB Fiddle :

with mytable as (select '{"values": [11, 22, 33]}'::jsonb column_name)
select jsonb_set(column_name, '{values}', new_values) new_column_name
from mytable t
left join lateral (
    select jsonb_agg(val) new_values
    from jsonb_array_elements(t.column_name -> 'values') x(val)
    where val::int <> 33
) x on 1 = 1
| new_column_name      |
| :------------------- |
| {"values": [11, 22]} |
...