postgresql: Как обновить JSONB, чтобы добавить новый ключ во вложенный массив - PullRequest
1 голос
/ 07 февраля 2020

Как обновить JSONB для добавления нового ключа во вложенный массив (для всех элементов массива) для всех записей.

Я имею в виду ссылку Структура таблицы :

CREATE TABLE orders (
    id   serial PRIMARY KEY,
    data jsonb
);

Дано json:

{
  "Number": "555",
  "UserId": "1",
  "Items": [
    {
      "ProductId": "1", 
      "Name": "TV",
      "Price": "300.00"
    }, 
    {
      "ProductId": "2", 
      "Name": "Mechanical Keyboard",
      "Price": "120.00"
    }
  ]
}

Для добавления нового элемента в каждый элемент массива выдается следующий запрос:

UPDATE orders
SET data = jsonb_set(
    data, 
    '{Items}',      -- the array in which we operate
    to_jsonb(
    (WITH ar AS(
      WITH temp AS(
        SELECT data->'Items' AS items   -- the array in which we operate
        FROM orders
        WHERE id = 1    -- the filtered order we are updating
      )
      SELECT jsonb_set(
        jsonb_array_elements(items),
        '{Quantity}',   -- the new field we are adding
        '"1"',          -- the value of the new field
        true)
      FROM temp)
     SELECT (array_agg(ar.jsonb_set))
     FROM ar)),
  false)
WHERE id = 1;

Вывод после выполнения запроса выше:

{
  "Number": "555",
  "UserId": "1",
  "Items": [
    {
      "ProductId": "1", 
      "Name": "TV",
      "Price": "300.00",
      "Quantity": "1"
    }, 
    {
      "ProductId": "2", 
      "Name": "Mechanical Keyboard",
      "Price": "120.00",
      "Quantity": "1"
    }
  ]
}

Но выше будет обновлять json только там, где id=1. Какие изменения необходимы для обновления JSON так же, как указано выше для всех строк в заказах?

Ответы [ 2 ]

1 голос
/ 07 февраля 2020

Вам не нужно делать это SELECT data->'Items' AS items FROM orders WHERE id = 1 CTE внутри оператора SET - вы можете просто обратиться к data->'Items' напрямую, и он займет текущую обновленную строку, как вы уже это сделали в data = jsonb_set(data, …). Таким образом, вы можете упростить до

UPDATE orders
SET data = jsonb_set(
    data, 
    '{Items}',      -- the array in which we operate
    (SELECT jsonb_agg(jsonb_set(
        item,
        '{Quantity}',   -- the new field we are adding
        '"1"',          -- the value of the new field
        true))
     FROM jsonb_array_elements(data->'Items')) AS item, -- the array in which we operate
  false)
WHERE id = 1;

(я также избавился от другого CTE и заменил to_jsonb(array_agg(…)) на jsonb_agg)

Теперь все, что вам нужно сделать для обновления всех строк, это пропуская предложение WHERE.

1 голос
/ 07 февраля 2020

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

create or replace function jsonb_insert_into_elements(jsonb, jsonb)
returns jsonb language sql immutable as $$
    select jsonb_agg(value || $2)
    from jsonb_array_elements($1)
$$;

Теперь обновление действительно простое и элегантное:

update orders
set data = jsonb_set(
    data, 
    '{Items}', 
    jsonb_insert_into_elements(data->'Items', '{"Quantity": "1"}'))
where id = 1 -- just skip this if you want to update all rows

Db <> Fiddle.

...