Как обновить значение внутри вложенных массивов в объекте jsonb? - PullRequest
1 голос
/ 17 апреля 2019

Я получил следующее jsonb содержимое в PostgreSQL:

{
    "segments": [
        {
            "type": "year",
            "settings": [
                {
                    "name": "length",
                    "value": "4"
                }
            ]
        },
        {
            "type": "month",
            "settings": [
                {
                    "name": "length",
                    "value": "2"
                }
            ]
        },
        {
            "type": "dayOfMonth",
            "settings": [
                {
                    "name": "length",
                    "value": "2"
                }
            ]
        },
        {
            "type": "autoIncrement",
            "settings": [
                {
                    "name": "scope",
                    "value": "plant"
                },
                {
                    "name": "period",
                    "value": "day"
                },
                {
                    "name": "length",
                    "value": "10"
                },
                {
                    "name": "paddingCharactor",
                    "value": "0"
                }
            ]
        }
    ]
}

, и я хочу обновить период с 'день' до 'навсегда' в сегментах -> type = autoIncrement -> settings-> период

Я попытался jsonb_set и вручную объединить данные после разделения, оба не удалось.

Есть ли какие-либо идеи для обновления значения?

Ответы [ 3 ]

0 голосов
/ 17 апреля 2019

Для обновления заданного значения jsonb по запросу:

WITH cte(j) AS (
   SELECT jsonb '{ ... }'
   )
SELECT jsonb_set(j, '{segments,-1,settings,1,value}'::text[], '"forever"')
FROM   cte;

дБ <> скрипка здесь

Но я не уверен, что вы задали правильный вопрос .
Сложная часть состоит в том, чтобы узнать положение массива в каждом вложенном массиве. Я взял последний элемент (-1) внешнего массива и второй (1) внутреннего. Вот как вы можете сделать это полностью динамическим с помощью чистого SQL:

Вместо этого рассмотрим нормализованный дизайн, , как предложил Каушик .

0 голосов
/ 19 апреля 2019

Спасибо за ответы, окончательное решение заключается в прямой замене содержимого json после приведения его к тексту, а затем приведения обратно к jsonb.

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

0 голосов
/ 17 апреля 2019

Хотя это возможно с крайне нечитаемым запросом, функция PostgreSQL jsonb_set не (пока) не может использовать JSONPath для выбора элемента для замены, но незавершенное выполнение связано с эта тема .

В настоящее время, вероятно, лучше написать вспомогательную функцию.

Поскольку я предпочитаю Python, вот мое мнение (достаточно общее, чтобы поддерживать различныесценарии, но достаточно адаптированные, чтобы их нельзя было использовать):

CREATE OR REPLACE FUNCTION jsonb_change_setting(val jsonb, segment_filter jsonb, setting text, replacement jsonb)
    RETURNS jsonb
    TRANSFORM FOR TYPE jsonb
    LANGUAGE plpython3u
AS $$
v_new = val
tmp = v_new["segments"]
for item in tmp:
    if (segment_filter.items() <= item.items()):
        settings = item["settings"]
        for s in settings:
            if (s["name"] == setting):
                s["value"] = replacement

return v_new
$$;

... и могут быть вызваны следующим образом для вашего требования:

UPDATE serial_rules
SET rule = jsonb_change_setting(config, '{"type":"autoIncrement"}', 'period', '"forever"')
WHERE ...

Помните, что для этой функции требуется PostgreSQL 11 и расширенияplpython3u и jsonb_plpython3u (для автоматической обработки jsonb).

Версия для PostgreSQL 9.6+ (где расширение преобразователя типа jsonb_plpython3u недоступно) будет:

CREATE OR REPLACE FUNCTION jsonb_change_setting(val jsonb, segment_filter jsonb, setting text, replacement jsonb)
    RETURNS jsonb
    LANGUAGE plpython3u
AS $$
import json
v_new = json.loads(val)
tmp = v_new["segments"]
for item in tmp:
    if (segment_filter.items() <= item.items()):
        settings = item["settings"]
        for s in settings:
            if (s["name"] == setting):
                s["value"] = replacement

return json.dumps(v_new)
$$;
...