Як виконувати операції по оновленню стовпців типу JSONB в Postgres 9.4


133

Переглядаючи документацію для типу даних Postgres 9.4 JSONB, мені не відразу зрозуміло, як робити оновлення для стовпців JSONB.

Документація для типів та функцій JSONB:

http://www.postgresql.org/docs/9.4/static/functions-json.html http://www.postgresql.org/docs/9.4/static/datatype-json.html

Як приклад, у мене є така основна структура таблиці:

CREATE TABLE test(id serial, data jsonb);

Вставлення легко, як у:

INSERT INTO test(data) values ('{"name": "my-name", "tags": ["tag1", "tag2"]}');

Тепер, як би я оновив стовпчик "дані"? Це недійсний синтаксис:

UPDATE test SET data->'name' = 'my-other-name' WHERE id = 1;

Це документоване десь очевидно, що я пропустив? Дякую.

Відповіді:


33

В ідеалі ви не використовуєте документи JSON для структурованих, регулярних даних, якими ви хочете маніпулювати всередині реляційної бази даних. Використовуйте замість цього нормалізовану реляційну конструкцію .

JSON в першу чергу призначений для зберігання цілих документів, якими не потрібно маніпулювати всередині RDBMS. Пов'язані:

Оновлення рядка в Postgres завжди пише нову версію всього рядка. Це основний принцип моделі постгреса MVCC . З точки зору продуктивності, навряд чи важливо, чи змінили ви один фрагмент даних усередині об'єкта JSON або все це: повинна бути записана нова версія рядка.

Таким чином, поради в посібнику :

Дані JSON підпадають під ті ж міркування щодо контролю одночасності, як і будь-який інший тип даних, коли вони зберігаються в таблиці. Хоча зберігання великих документів можливо, майте на увазі, що будь-яке оновлення набуває блокування на рівні рядка для всього рядка. Розгляньте можливість обмеження документів JSON на керований розмір, щоб зменшити суперечку блокування серед оновлених транзакцій. В ідеалі документи JSON повинні представляти собою атомну дату, яку диктують ділові правила, не можна розумно підрозділити на менші дані, які можна було б змінити самостійно.

Суть його: щоб змінити що- небудь всередині об'єкта JSON, вам потрібно призначити модифікований об’єкт стовпцю. Postgres постачає обмежені засоби для збирання та маніпулювання jsonданими, крім своїх можливостей зберігання. З кожним новим випуском з версії 9.2 арсенал інструментів істотно зростав. Але головне залишається: Ви завжди повинні призначити повний модифікований об'єкт стовпцю, і Postgres завжди пише нову версію рядка для будь-якого оновлення.

Деякі методи роботи з інструментами Postgres 9.3 або новіших версій:

Ця відповідь привернув приблизно стільки ж downvotes , як і всі інші мої відповіді на SO разом . Людям не подобається ідея: нормалізована конструкція перевершує нединамічні дані. Цей чудовий допис у блозі Крейга Рінгера пояснює більш докладно:


6
Ця відповідь стосується лише типу JSON і ігнорує JSONB.
fiatjaf

7
@fiatjaf: Ця відповідь повністю застосовна для типів даних jsonі jsonbподібних. Обидва зберігають дані JSON, jsonbробить це у нормалізованій бінарній формі, яка має деякі переваги (і мало недоліків). stackoverflow.com/a/10560761/939860 Жоден тип даних не є хорошим для маніпулювання великою кількістю всередині бази даних. Немає типу документа. Ну, це добре для невеликих, майже не структурованих документів JSON. Але великі, вкладені документи були б таким глупотом.
Erwin Brandstetter

7
"Інструкції щодо роботи з інструментами Postgres 9.3" дійсно повинні бути першими у вашій відповіді, оскільки вона відповідає на задане питання. Іноді має сенс оновити json для технічного обслуговування / змін схеми тощо та причини, щоб не робити оновлення json don Не дійсно
Майкл Вассер

22
Ця відповідь не корисна, вибачте. @jvous, чи ти не хочеш прийняти відповідь Джимоті, адже це справді відповідає на твоє запитання?
Бастіан Фойтт

10
Спершу дайте відповідь на питання, перш ніж додавати власний коментар / думку / дискусію.
Ppp

332

Якщо ви зможете оновити до Postgresql 9.5, jsonb_setкоманда доступна, як уже згадували інші.

У кожному з наступних операторів SQL я пропустив whereпункт про короткість; очевидно, ви хочете додати це назад.

Оновити назву:

UPDATE test SET data = jsonb_set(data, '{name}', '"my-other-name"');

Замініть теги (на відміну від додавання або видалення тегів):

UPDATE test SET data = jsonb_set(data, '{tags}', '["tag3", "tag4"]');

Заміна другого тегу (0-індексованого):

UPDATE test SET data = jsonb_set(data, '{tags,1}', '"tag5"');

Додайте тег ( це буде працювати до тих пір, поки буде менше 999 тегів; зміна аргументу 999 на 1000 або вище генерує помилку . Це більше не відбувається у Postgres 9.5.3; можна використовувати набагато більший індекс) :

UPDATE test SET data = jsonb_set(data, '{tags,999999999}', '"tag6"', true);

Видаліть останній тег:

UPDATE test SET data = data #- '{tags,-1}'

Комплексне оновлення (видалити останній тег, вставити новий тег та змінити ім'я):

UPDATE test SET data = jsonb_set(
    jsonb_set(data #- '{tags,-1}', '{tags,999999999}', '"tag3"', true), 
    '{name}', '"my-other-name"');

Важливо зазначити, що в кожному з цих прикладів ви фактично не оновлюєте жодного поля даних JSON. Натомість ви створюєте тимчасову, модифіковану версію даних та призначаєте цю змінену версію до стовпця. На практиці результат повинен бути однаковим, але враховуючи це, слід зробити складніші оновлення, як останній приклад, більш зрозумілими.

У складному прикладі є три перетворення та три тимчасові версії: Спочатку видаляється останній тег. Потім ця версія перетворюється, додаючи новий тег. Далі, друга версія перетворюється шляхом зміни nameполя. Значення в dataстовпці замінюється на остаточну версію.


42
ви отримуєте бонусні бали за те, як показувати, як оновити стовпчик у таблиці, як вимагає ОП
chadrik

1
@chadrik: Я додав більш складний приклад. Це не робить саме те, що ви просили, але це повинно дати вам уявлення. Зауважте, що вхід для зовнішнього jsonb_setдзвінка - це вихід із внутрішнього дзвінка, і що вхід до цього внутрішнього дзвінка є результатом data #- '{tags,-1}'. Тобто вихідні дані з останнім тегом видалено.
Джімоті

1
@PranaySoni: З цією метою я, мабуть, використовував би збережену процедуру або, якщо накладні витрати не викликають проблем, повернути ці дані назад, маніпулювати ними мовою програми, а потім записати їх назад. Це звучить важко, але майте на увазі, у всіх наведених нами прикладах ви все ще не оновлюєте жодного поля в JSON (B): ви перезаписуєте весь стовпець у будь-якому випадку. Таким чином, зберігається Proc насправді не відрізняється.
Джимофій

1
@ Алекс: Так, трохи хак. Якби я сказав {tags,0}, це означало б "перший елемент масиву tags", що дозволить мені надати нове значення цьому елементу. Використовуючи велику кількість замість 0, замість того, щоб замінити існуючий елемент у масиві, він додає новий масив до масиву. Однак, якщо в масиві насправді було більше 999 999 999 елементів, це замінить останній елемент, а не додати новий.
Джимотій

1
а як бути, якщо поле містить null? виглядає не 'робота. Наприклад, поле jsonb info є нульовим: "UPDATE organizator SET info = jsonb_set (info, '{country}', '" FRA "'), де інформація - >> 'country' :: текст IS NULL;" Я отримую UPDATE 105 запис, але немає змін на db
stackdave

24

Це надходить у 9.5 у формі jsonb_set від Ендрю Данстан на основі наявного розширення jsonbx , яке працює з 9.4


Іншим питанням у цьому рядку є використання jsonb_build_object(), оскільки x->key, не повертає пару ключів-об’єктів, для заповнення вам потрібно jsonb_set(target, path, jsonb_build_object('key',x->key)).
Пітер Краусс

19

Для тих, хто стикається з цією проблемою і хоче дуже швидко виправити (і застряг 9.4.5 або раніше), ось що я зробив:

Створення тестової таблиці

CREATE TABLE test(id serial, data jsonb);
INSERT INTO test(data) values ('{"name": "my-name", "tags": ["tag1", "tag2"]}');

Оновіть оператор, щоб змінити ім'я властивості jsonb

UPDATE test 
SET data = replace(data::TEXT,'"name":','"my-other-name":')::jsonb 
WHERE id = 1;

Зрештою, прийнята відповідь правильна тим, що ви не можете змінити окремий фрагмент об'єкта jsonb (в 9.4.5 або раніше); однак, ви можете кинути об’єкт jsonb до рядка (:: TEXT), а потім маніпулювати рядком і повернутись до об'єкта jsonb (:: jsonb).

Є два важливих застереження

  1. це замінить усі властивості, звані "ім'ям" у json (якщо у вас є декілька властивостей з тим самим іменем)
  2. це не так ефективно, як jsonb_set було б, якщо ви використовуєте 9.5

З урахуванням цього я зіткнувся з ситуацією, коли мені довелося оновити схему вмісту в об'єктах jsonb, і це був найпростіший спосіб виконати саме те, про що писав оригінальний плакат.


1
Добрий пане, я шукав, як зробити оновлення до jsonb, як дві години, щоб я міг замінити всі \u0000нульові символи, наприклад показав повну картину. Дякую за це!
Джошуа Робінсон

3
виглядає добре! btw Другий аргумент для заміни у вашому прикладі включає двокрапку, а третій - ні. Схоже, ваш дзвінок повинен бутиreplace(data::TEXT, '"name":', '"my-other-name":')::jsonb
davidicus

Дякую @davidicus! Вибачте за дуже запізніле оновлення, але я вдячний, що ви поділилися з іншими!
Чад Капра

12

Це питання було задано в контексті постгресів 9.4, однак новим глядачам, які приходять до цього питання, слід знати, що в postgres 9.5, піддокумент Створення / оновлення / видалення операцій у полях JSONB підтримується базою даних без необхідності розширення функції.

Див.: Оператори та функції, що змінюють JSONB


8

оновіть атрибут 'name':

UPDATE test SET data=data||'{"name":"my-other-name"}' WHERE id = 1;

і якщо ви хочете, наприклад, видалити атрибути "name" та "теги":

UPDATE test SET data=data-'{"name","tags"}'::text[] WHERE id = 1;

6

Я написав для себе невелику функцію, яка працює рекурсивно в Postgres 9.4. У мене була така ж проблема (добре, що вони вирішили частину цього головного болю в Postgres 9.5). У будь-якому випадку є функція (сподіваюся, вона добре працює для вас):

CREATE OR REPLACE FUNCTION jsonb_update(val1 JSONB,val2 JSONB)
RETURNS JSONB AS $$
DECLARE
    result JSONB;
    v RECORD;
BEGIN
    IF jsonb_typeof(val2) = 'null'
    THEN 
        RETURN val1;
    END IF;

    result = val1;

    FOR v IN SELECT key, value FROM jsonb_each(val2) LOOP

        IF jsonb_typeof(val2->v.key) = 'object'
            THEN
                result = result || jsonb_build_object(v.key, jsonb_update(val1->v.key, val2->v.key));
            ELSE
                result = result || jsonb_build_object(v.key, v.value);
        END IF;
    END LOOP;

    RETURN result;
END;
$$ LANGUAGE plpgsql;

Ось приклад використання:

select jsonb_update('{"a":{"b":{"c":{"d":5,"dd":6},"cc":1}},"aaa":5}'::jsonb, '{"a":{"b":{"c":{"d":15}}},"aa":9}'::jsonb);
                            jsonb_update                             
---------------------------------------------------------------------
 {"a": {"b": {"c": {"d": 15, "dd": 6}, "cc": 1}}, "aa": 9, "aaa": 5}
(1 row)

Як бачите, проаналізуйте в глибині душі та оновіть / додайте значення, де це необхідно.


Це не працює в 9.4, тому що jsonb_build_objectбуло введено в 9.5
Грег

@Greg Ви маєте рацію, я щойно перевірив і зараз запускаю PostgreSQL 9.5 - ось чому це працює. Дякую, що вказали на це - моє рішення не працюватиме в 9.4.
J. Raczkiewicz

4

Можливо: UPDATE test SET data = '"my-other-name"' :: json WHERE id = 1;

Це працювало з моїм випадком, коли дані - тип json


1
Працював і для мене, на postgresql 9.4.5. Весь запис переписаний, тому не можна оновлювати жодне поле атм.
kometen

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.