一种通用的方法是分解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