1 Общий способ сделать это - взорвать json, заменить значения, используя обычный старый sql, и агрегировать обратно в исходную форму json.Но это требует от вас полного знания структуры документа
Вот пример этого в отдельном операторе выбора
WITH data(map) AS (
VALUES(JSONB '{"continents":[{"id": 1,"name": "North America","countries": [{"id": 1,"name": "USA","subdivision": [{"id": 1,"name": "Oregon","type": "SOME_TYPE"}]}]}]}')
)
, expanded AS (
SELECT
(continents#>>'{id}')::int continent_id
, continents#>>'{name}' continent_name
, (countries#>>'{id}')::int country_id
, countries#>>'{name}' country_name
, (subdivisions#>>'{id}')::int subdivision_id
, subdivisions#>>'{name}' subdivision_name
, CASE WHEN subdivisions#>>'{type}' = 'SOME_TYPE' -- put all update where conditions here
AND continents#>>'{name}' = 'North America' -- this is where the value is changed
THEN 'POTATO'
ELSE subdivisions#>>'{type}'
END subdivision_type
FROM data
, JSONB_ARRAY_ELEMENTS(map#>'{continents}') continents
, JSONB_ARRAY_ELEMENTS(continents#>'{countries}') countries
, JSONB_ARRAY_ELEMENTS(countries#>'{subdivision}') subdivisions
)
, subdivisions AS (
SELECT continent_id
, continent_name
, country_id
, country_name
, JSONB_BUILD_OBJECT('subdivisions', JSONB_AGG(JSONB_BUILD_OBJECT('id', subdivision_id, 'name', subdivision_name, 'type', subdivision_type))) subdivisions
FROM expanded
GROUP By 1, 2, 3, 4
)
, countries AS (
SELECT
continent_id
, continent_name
, JSONB_BUILD_OBJECT('countries', JSONB_AGG(JSONB_BUILD_OBJECT('id', country_id, 'name', country_name, 'subdivision', subdivisions))) countries
FROM subdivisions
GROUP BY 1, 2
)
SELECT JSONB_BUILD_OBJECT('continents', JSONB_AGG(JSONB_BUILD_OBJECT('id', continent_id, 'name', continent_name, 'countries', countries))) map
FROM countries
Поместив это в запрос на обновление, мы получим следующеегде я предполагаю, что исходная таблица называется data
, и у нее есть уникальный столбец с именем id
UPDATE data SET map = updated.map
FROM (
expanded AS (
SELECT data.id data_id
, (continents#>>'{id}')::int continent_id
, continents#>>'{name}' continent_name
, (countries#>>'{id}')::int country_id
, countries#>>'{name}' country_name
, (subdivisions#>>'{id}')::int subdivision_id
, subdivisions#>>'{name}' subdivision_name
, CASE WHEN subdivisions#>>'{type}' = 'SOME_TYPE'
AND continents#>>'{name}' = 'North America'
THEN 'POTATO'
ELSE subdivisions#>>'{type}'
END subdivision_type
FROM data
, JSONB_ARRAY_ELEMENTS(map#>'{continents}') continents
, JSONB_ARRAY_ELEMENTS(continents#>'{countries}') countries
, JSONB_ARRAY_ELEMENTS(countries#>'{subdivision}') subdivisions
)
, subdivisions AS (
SELECT
data_id
, continent_id
, continent_name
, country_id
, country_name
, JSONB_BUILD_OBJECT('subdivisions', JSONB_AGG(JSONB_BUILD_OBJECT('id', subdivision_id, 'name', subdivision_name, 'type', subdivision_type))) subdivisions
FROM expanded
GROUP By 1, 2, 3, 4, 5
)
, countries AS (
SELECT
data_id
, continent_id
, continent_name
, JSONB_BUILD_OBJECT('countries', JSONB_AGG(JSONB_BUILD_OBJECT('id', country_id, 'name', country_name, 'subdivision', subdivisions))) countries
FROM subdivisions
GROUP BY 1, 2, 3
)
SELECT data_id, JSONB_BUILD_OBJECT('continents', JSONB_AGG(JSONB_BUILD_OBJECT('id', continent_id, 'name', continent_name, 'countries', countries))) map
FROM countries
GROUP BY 1
) updated
WHERE updated.data_id = data.id