【问题标题】:How to update a value inside nested arrays in a jsonb object?如何更新 jsonb 对象的嵌套数组中的值?
【发布时间】:2019-04-17 14:50:59
【问题描述】:

我在 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并在拆分后手动合并数据,都失败了。

有更新值的想法吗?

【问题讨论】:

  • 表名serial_rules,字段名rule
  • 并不是说不能这样做,但是如果您有兴趣定期操作其更深层次的内容,我建议不要将您的数据作为 JSON 存储在数据库中。使用简单的关系表单独存储各个元素,这样更容易处理。

标签: sql json postgresql jsonb


【解决方案1】:

虽然可能有一个高度不可读的查询,但 PostgreSQL jsonb_set 函数(还)不能利用 JSONPath 来选择要替换的元素,但有 work in-progress 与 this topic 相关。

目前,最好写一个辅助函数。

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

【讨论】:

    【解决方案2】:

    根据要求更新给定的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。

    【讨论】:

      【解决方案3】:

      感谢大家的解答,最终的解决方案是直接将json内容转换为text后再转换回jsonb。

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

      【讨论】:

      • 想象一个生产环境,JSONB 字段中有大量记录和大量文本。祝你好运用这种方法清理这个烂摊子。
      猜你喜欢
      • 1970-01-01
      • 2017-09-22
      • 2018-11-15
      • 2021-11-29
      • 2015-05-20
      • 2017-11-26
      • 1970-01-01
      • 2021-11-27
      • 2021-02-06
      相关资源
      最近更新 更多