Как увеличить значение в инструкции обновления postgres для ключа JSON

При обновлении реляционной таблицы:

CREATE TABLE foo ( id serial primary key, credit numeric);
UPDATE foo SET bar = bar + $1 WHERE id = $2;

Однако эквивалент в JSON не работает:

CREATE TABLE foo ( id serial primary key, data json);
UPDATE foo SET data->'bar' = data->'bar' + $1 WHERE id = $2;

Ошибка, которую я получаю, это error: syntax error at or near "->" - это довольно неоднозначно.

Как это сделать?

Я использую postgres 9.3.4


В свете комментария @GordonLinoff ниже я создал запрос функции: https://postgresql.uservoice.com/forums/21853-general/suggestions/6466818-create-update-delete-on-json-keys

Вы можете проголосовать, если хотите эту функцию.

Ответ 1

Вы можете сделать это с помощью jsonb, по крайней мере, с помощью Postgres 9.5.2.

Учитывая следующую таблицу:

CREATE TABLE users (id INT, counters JSONB NOT NULL DEFAULT '{}');

С данными образца:

INSERT INTO users (id, counters) VALUES (1, '{"bar": 0}');

SELECT * FROM users;

 id |    counters
----+------------
  1 | {"bar": 0}

Вы можете увеличить "бар" в JSON атомарно:

UPDATE users SET counters = counters || CONCAT('{"bar":', COALESCE(counters->>'bar','0')::int + 1, '}')::jsonb WHERE id = 1;

SELECT * FROM users;

 id |    counters
----+------------
  1 | {"bar": 1}

Это не красиво, но оно работает.

Здесь он разбит по этапам:

Вы можете установить ключ в jsonb на явное значение OR, используя объекты jsonb:

UPDATE users SET counters = counters || '{"bar": 314}'::jsonb WHERE id = 1;

SELECT * FROM users;

 id |     counters
----+--------------
  1 | {"bar": 314}

Теперь все, что осталось сделать, это динамически построить строку с помощью CONCAT(), одновременно демонстрируя приращение (на 27) клавиши undefined (по умолчанию начальное значение с помощью COALESCE()):

UPDATE users SET counters = counters || CONCAT('{"foo":', COALESCE(counters->>'foo','0')::int + 27, '}')::jsonb WHERE id = 1;

SELECT * FROM users;

 id |          counters
----+-------------------------
  1 | {"bar": 314, "foo": 27}

Боб твой дядя.:)

Ответ 2

На основе ответов @joonas.fi и pozs я придумал немного более "красивое" решение

UPDATE foo 
SET data = jsonb_set(data, '{bar}', (COALESCE(data->>'bar','0')::int + 1)::text::jsonb)
WHERE id = 1;