我在PostgreSQL 9.6中有一个jsonb结构,其中包含类似于下面示例的嵌套数组结构:
continents:[
{
id: 1,
name: 'North America',
countries: [
{
id: 1,
name: 'USA',
subdivision: [
{
id: 1,
name: 'Oregon',
type: 'SOME_TYPE'
}
]
}
]
}
]
我如何更改多个细分的'type'属性,因为它嵌套在两个数组( countries 和 subdivision )中?
我遇到了其他答案,并且能够逐条记录地进行操作(假设表是 map ,而jsonb列是 divisions ):>
update map
set divisions = jsonb_set( divisions, '{continents,0,countries,0,subdivisions,0,type}', '"STATE"', FALSE);
是否可以通过编程方式更改所有细分的属性?
我想我已经接近了,我可以使用下面的查询查询所有细分类型,但正在努力找出如何更新它们:
WITH subdivision_data AS (
WITH country_data AS (
select continents -> 'countries' as countries
from map, jsonb_array_elements( map.divisions -> 'continents' ) continents
)
select country_item -> 'subdivisions' as subdivisions
from country_data cd, jsonb_array_elements( cd.countries ) country_item
)
select subdivision_item ->> 'type' as subdivision_type
from subdivision_data sub, jsonb_array_elements( sub.subdivisions ) subdivision_item;
以下是我遇到的一些问题。但是,它们似乎仅在您尝试更新单级数组时才起作用:
postgresql 9.5 using jsonb_set for updating specific jsonb array value
How to update deeply nested JSON object based on filter criteria in Postgres?
答案 0 :(得分:0)
执行此操作的一种通用方法是爆炸 json,使用普通的旧sql替换值并聚合回原始json形状。但这需要您全面了解文档结构
这是一个独立的select语句中的例子
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
答案 1 :(得分:0)
起初我以为这样的事情会起作用:
update map as m set
divisions = jsonb_set(m1.divisions, array['continents',(d.rn-1)::text,'countries',(c.rn-1)::text,'subdivisions',(s.rn-1)::text,'type'], '"STATE"', FALSE)
from map as m1,
jsonb_array_elements(m1.divisions -> 'continents') with ordinality as d(data,rn),
jsonb_array_elements(d.data -> 'countries') with ordinality as c(data,rn),
jsonb_array_elements(c.data -> 'subdivisions') with ordinality as s(data,rn)
where
m1.id = m.id
但这不起作用-请参见documentation
当存在FROM子句时,实际上发生的是 目标表已加入from_list中提到的表,并且 联接的每个输出行代表 目标表。使用FROM时,应确保联接产生 每个要修改的行最多有一个输出行。换句话说, 目标行不应与另一行连接在一起 表格。如果是这样,则仅连接行之一将用于 更新目标行,但是使用哪一行并不容易 可预测的。
您可以做的是用functions-json
将json嵌套,然后将它们聚合回去:
update map set
divisions = jsonb_set(divisions, array['continents'],
(select
jsonb_agg(jsonb_set(
d, array['countries'],
(select
jsonb_agg(jsonb_set(
c, array['subdivisions'],
(select
jsonb_agg(jsonb_set(s, array['type'], '"STATE"', FALSE))
from jsonb_array_elements(c -> 'subdivisions') as s),
FALSE
))
from jsonb_array_elements(d -> 'countries') as c)
))
from jsonb_array_elements(divisions -> 'continents') as d),
FALSE
)
您还可以创建可用于代替多个子查询的辅助函数:
create function jsonb_update_path(_data jsonb, _path text[], _value jsonb)
returns jsonb
as $$
begin
if array_length(_path, 1) = 1 then
return jsonb_set(_data, _path, _value, FALSE);
else
return (
jsonb_set(
_data, _path[1:1],
(
select
jsonb_agg(jsonb_update_path(e, _path[2:], _value))
from jsonb_array_elements(_data -> _path[1]) as e
)
)
);
end if;
end
$$
language plpgsql
update map set
divisions = jsonb_update_path(divisions, '{continents,countries,subdivisions,type}', '"STATE"')