问题
I have a table mapping_transform with a JSONB column content_json containing something like
{
"meta": {...},
"mapping": [
...,
{
"src": "up",
"dest": "down",
...
},
...
]
}
I want to add a new JSON entry ("rule_names": [ "some name" ]) to the JSON object matching src = up and dest = down, which would result in
{
"meta": {...},
"mapping": [
...,
{
"src": "up",
"dest": "down",
...,
"rule_names": [ "some name" ]
},
...
]
}
The following query returns the JSON object that meets the filter requirements:
WITH elems AS (SELECT json_array_elements(content_json->'mapping') from mapping_transform)
SELECT * FROM elems WHERE json_array_elements->>'src' = 'up' and json_array_elements->>'dest' = 'down';
-- Alternative
SELECT mt_entry
FROM mapping_transform,
LATERAL jsonb_array_elements(content_json::jsonb->'mapping') mt_entry
WHERE mt_entry->>'src' = 'up' and mt_entry->>'dest' = 'down';
My problem now is that I do not know how to add the new entry to the specific object. I tried something like
WITH elems AS (SELECT json_array_elements(content_json->'mapping') from mapping_transform),
results SELECT * FROM elems WHERE json_array_elements->>'src' = 'up' and json_array_elements->>'dest' = 'down'
UPDATE mapping_transform
SET content_json = jsonb_set(results, '{"rule_names"}', '["some name"]'); -- this does obviously not work
but that does not execute as results is an unknown column. I also do need to merge the result of the jsonb_set with the rest of the content_json before assigning to content_json, because otherwise it would override the whole content.
How can I update specific deeply nested JSON objects based on filter criteria? If I had a well defined path as to where my object is that I want to update, things would be much easier. But as the target object lies within a JSON array and has an arbitrary position, finding and updating it is much more difficult.
回答1:
If you are familiar with JavaScript you'll be happy to install and use JavaScript procedural language plv8. This extension allows you to modify json values natively, example:
create extension if not exists plv8;
create or replace function update_mapping_v8(data json)
returns json language plv8 as $$
var len = data['mapping'].length;
for (var i = 0; i < len; i++) {
var o = data['mapping'][i];
if (o.src == 'up' && o.dest == 'down') {
o.rule_names = 'some name'
}
}
return data;
$$;
update mapping_transform
set content_json = update_mapping_v8(content_json);
For MS Windows users: ready to install Windows binaries.
A plpgsql alternative solution uses jsonb type:
create or replace function update_mapping_plpgsql(data jsonb)
returns json language plpgsql as $$
declare
r record;
begin
for r in
select value, ordinality- 1 as pos
from jsonb_array_elements(data->'mapping') with ordinality
where value->>'src' = 'up' and value->>'dest' = 'down'
loop
data = jsonb_set(
data,
array['mapping', r.pos::text],
r.value || '{"rule_names": "some name"}'
);
end loop;
return data;
end $$;
update mapping_transform
set content_json = update_mapping_plpgsql(content_json::jsonb);
回答2:
I build path here: concat('{mapping,',(ord::int-1),'}')::text[] and the rest is pretty same. Please note I join on text=text (because I dont know what is your PK - it is not recommended). left the value to update with, right original:
vao=# with num as (select content_json,val,ord from mapping_transform, json_array_elements(content_json->'mapping') with ordinality as o (val,ord) where val->>'src' = 'up')
select
jsonb_pretty(
jsonb_set(t.content_json::jsonb,concat('{mapping,',(ord::int-1),'}')::text[],((t.content_json->'mapping'->(ord::int-1))::jsonb||'{"rule_names":["some name"]}')::jsonb)
)
, jsonb_pretty(t.content_json::jsonb)
from mapping_transform t
join num on num.content_json::text = t.content_json::text
/* of course join should be on PK, not text representation*/
;
jsonb_pretty | jsonb_pretty
-----------------------------+----------------------------
{ +| { +
"meta": { +| "meta": { +
"a": true +| "a": true +
}, +| }, +
"mapping": [ +| "mapping": [ +
"a", +| "a", +
"c", +| "c", +
{ +| { +
"a": 0, +| "a": 0, +
"src": "up", +| "src": "up", +
"dest": "down",+| "dest": "down"+
"rule_names": [+| }, +
"some name"+| "b" +
] +| ] +
}, +| }
"b" +|
] +|
} |
{ +| { +
"meta": { +| "meta": { +
"a": true +| "a": true +
}, +| }, +
"mapping": [ +| "mapping": [ +
"a", +| "a", +
{ +| { +
"a": 0, +| "a": 0, +
"src": "up", +| "src": "up", +
"dest": "down",+| "dest": "down"+
"rule_names": [+| }, +
"some name"+| "b" +
] +| ] +
}, +| }
"b" +|
] +|
} |
(2 rows)
and the build:
vao=# create table mapping_transform(content_json jsonb);
CREATE TABLE
vao=# insert into mapping_transform select '{
"meta": {
"a": true
},
"mapping": ["a",{
"src": "up",
"dest": "down",
"a": 0
},
"b"
]
}';
INSERT 0 1
vao=# insert into mapping_transform select '{
"meta": {
"a": true
},
"mapping": ["a","c",{
"src": "up",
"dest": "down",
"a": 0
},
"b"
]
}';
INSERT 0 1
来源:https://stackoverflow.com/questions/45035597/how-to-update-deeply-nested-json-object-based-on-filter-criteria-in-postgres