如何在jsonb对象中的嵌套数组内更新值?

时间:2019-04-17 14:50:59

标签: sql json postgresql jsonb

我在PostgreSQL中获得了以下jsonb内容:

{
    "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"
                }
            ]
        }
    ]
}

我想在 segments-> type = autoIncrement-> settings-> period

中将“ day”的时间更新为“ forever”

我尝试过jsonb_set并在拆分后手动合并数据,但都失败了。

有什么想法更新值吗?

3 个答案:

答案 0 :(得分:0)

尽管对于高度不可读的查询来说,这是可能的,但PostgreSQL jsonb_set函数尚不能利用JSONPath来选择要替换的元素,但是有work in-progress与{{3} }。

暂时最好编写一个辅助函数。

我更喜欢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)
$$;

答案 1 :(得分:0)

要根据请求更新给定的jsonb值:

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

db <>提琴here

但是我不确定您是否一直在问正确的问题
困难的部分是要知道每个嵌套数组中的数组位置。我采用了外部数组的最后一个元素(-1)和内部数组的第二个元素(1)。这是使用纯SQL使其完全动态的方法:

请考虑使用标准化设计like Kaushik suggested

答案 2 :(得分:0)

感谢您的回答,最终的解决方案是将json内容转换为文本后直接替换为jsonb,然后直接替换。

问题在于如何检测哪个元素是要在数组中更新的正确元素,并且单个结构具有两层数组。因此,我认为替换其内容的最佳方法是使用替换功能。