Как выполнять операции обновления столбцов типа 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;

Это где-то явно задокументировано, что я пропустил? Спасибо.

jvous
источник

Ответы:

34

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

JSON в первую очередь предназначен для хранения целых документов, которыми не нужно управлять внутри СУБД. Связанный:

При обновлении строки в Postgres всегда записывается новая версия всей строки. Это основной принцип модели MVCC Постгреса . С точки зрения производительности вряд ли имеет значение, изменяете ли вы отдельный фрагмент данных внутри объекта JSON или все его: необходимо написать новую версию строки.

Таким образом, совет в руководстве :

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

Суть его: изменить что-либо внутри объекта JSON, вы должны назначить измененный объект столбцу. Postgres предоставляет ограниченные средства для создания jsonданных и управления ими в дополнение к своим возможностям хранения. Арсенал инструментов существенно расширялся с каждым новым выпуском, начиная с версии 9.2. Но принцип остается: вы всегда должны назначать столбцу полностью измененный объект, а Postgres всегда записывает новую версию строки для любого обновления.

Некоторые методы работы с инструментами Postgres 9.3 или новее:

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

Эрвин Брандштеттер
источник
6
Этот ответ касается только типа JSON и игнорирует JSONB.
fiatjaf
7
@fiatjaf: этот ответ полностью применим к типам данных jsonи jsonbт.п. Оба хранят данные JSON jsonbв нормализованной двоичной форме, которая имеет некоторые преимущества (и несколько недостатков). stackoverflow.com/a/10560761/939860 Ни один из типов данных не подходит для большого количества манипуляций внутри базы данных. Нет типа документа. Что ж, это нормально для небольших, плохо структурированных документов JSON. Но в таком случае большие вложенные документы были бы глупостью.
Эрвин Брандштеттер
7
«Инструкции по работе с инструментами Postgres 9.3» действительно должны быть первым в вашем ответе, поскольку он отвечает на заданный вопрос ... иногда имеет смысл обновить json для обслуживания / изменения схемы и т. Д. И причины не обновлять json don На самом деле не применимо
Майкл Вассер
22
Этот ответ бесполезен, извините. @jvous, ты не хочешь принять ответ Джимоти, ведь он действительно отвечает на твой вопрос?
Bastian Voigt
10
Сначала ответьте на вопрос, прежде чем добавлять свой комментарий / мнение / обсуждение.
Ppp
334

Если вы можете обновить до 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столбце заменяется окончательной версией.

Jimothy
источник
42
вы получаете бонусные баллы за то, что показываете, как обновить столбец в таблице в соответствии с запросом OP
chadrik
1
@chadrik: Я добавил более сложный пример. Это не совсем то, что вы просили, но оно должно дать вам представление. Обратите внимание, что вход для внешнего jsonb_setвызова является выходом из внутреннего вызова, а вход для этого внутреннего вызова является результатом data #- '{tags,-1}'. Т.е. исходные данные с удаленным последним тегом.
Jimothy
1
@PranaySoni: Для этой цели я бы, вероятно, использовал хранимую процедуру или, если накладные расходы не являются проблемой, вернул эти данные, обработал их на языке приложения, а затем записал их обратно. Звучит тяжеловато, но имейте в виду, что во всех приведенных мною примерах вы по-прежнему не обновляете ни одно поле в JSON (B): в любом случае вы перезаписываете весь столбец. Таким образом, сохраненная процедура ничем не отличается.
Джимоти,
1
@Alex: Да, немного взлома. Если бы я сказал {tags,0}, это означало бы «первый элемент массива tags», что позволило бы мне дать новое значение этому элементу. Используя большое число вместо 0, вместо замены существующего элемента в массиве, он добавляет новый элемент в массив. Однако, если в массиве действительно было более 999 999 999 элементов, это заменило бы последний элемент, а не добавило бы новый.
Jimothy
1
как насчет того, если поле содержит нуль? выглядит не работает. Например, поле info jsonb имеет значение NULL: "UPDATE организатор SET info = jsonb_set (info, '{country}', '" FRA "'), где info - >> 'country' :: text IS NULL;" Я получаю UPDATE 105 запись, но без изменений по db
stackdave
24

Это появится в версии 9.5 в виде появится в версии jsonb_set от Эндрю Данстана на основе существующего расширения jsonbx, которое работает с 9.4.

philofinfinitejest
источник
Другой проблемой в этой строке является использование jsonb_build_object(), потому что x->key, not возвращает пару ключ-объект, для заполнения, которое вам нужно 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. это заменит все свойства с именем "name" в json (в случае, если у вас есть несколько свойств с одинаковым именем)
  2. это не так эффективно, как jsonb_set, если вы используете 9.5

При этом я столкнулся с ситуацией, когда мне пришлось обновить схему для содержимого в объектах jsonb, и это был самый простой способ выполнить именно то, что просил исходный плакат.

Чад Капра
источник
1
Господи, я искал, как обновить jsonb около двух часов, чтобы я мог заменить все \u0000нулевые символы, пример показал полную картину. Спасибо за это!
Джошуа Робинсон
3
выглядит хорошо! Кстати, второй аргумент для замены в вашем примере включает двоеточие, а третий - нет. Похоже, ты должен позвонитьreplace(data::TEXT, '"name":', '"my-other-name":')::jsonb
Давидикус
Спасибо @davidicus! Извините за очень задержку обновления, но я благодарен вам за то, что вы поделились с другими!
Chad Capra
12

Этот вопрос был задан в контексте postgres 9.4, однако новые зрители, приходящие к этому вопросу, должны знать, что в postgres 9.5 операции создания / обновления / удаления субдокумента в полях JSONB изначально поддерживаются базой данных без необходимости расширения функции.

См .: Операторы и функции изменения JSONB

bguiz
источник
8

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

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

и если вы хотите удалить, например, атрибуты 'name' и 'tags':

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.
4

Возможно: UPDATE test SET data = '"my-other-name"' :: json WHERE id = 1;

Это сработало с моим случаем, где данные представляют собой тип json

Джанлуиджи Сартори
источник
1
У меня тоже работал на postgresql 9.4.5. Переписывается вся запись, поэтому невозможно обновить одно поле atm.
kometen