在 PostgreSQL 中合并 JSONB 值?

Merging JSONB values in PostgreSQL?

使用 || 运算符会产生以下结果:

select '{"a":{"b":2}}'::jsonb || '{"a":{"c":3}}'::jsonb ;
    ?column?     
-----------------
 {"a": {"c": 3}}
(1 row)

我希望能够实现以下结果(?? 只是运算符的占位符):

select '{"a":{"b":2}}'::jsonb ?? '{"a":{"c":3}}'::jsonb ;
    ?column?     
-----------------
 {"a": {"b": 2, "c": 3}}
(1 row)

因此,您可以看到顶级 a 键具有其子值 "merged",因此结果包含 bc

如何 "deep" 在 Postgres 中合并两个 JSONB 值?

这可能吗,如果可能怎么办?


更复杂的测试用例:

select '{"a":{"b":{"c":3},"z":true}}'::jsonb ?? '{"a":{"b":{"d":4},"z":false}}'::jsonb ;
    ?column?     
-----------------
 {"a": {"b": {"c": 3, "d": 4}, "z": false}}
(1 row)

另一个测试用例,其中原语 "merges over" 和对象:

select '{"a":{"b":{"c":3},"z":true}}'::jsonb ?? '{"a":{"b":false,"z":false}}'::jsonb ;
        ?column?         
-----------------
 {"a": {"b": false, "z": false}}
(1 row)

在 PostgreSQL 9.5 之后你可以使用 jsonb_set 函数:

  1. '{a,c}' 查看路径,如果不存在,将创建它。
  2. '{"a":{"c":3}}'::jsonb#>'{a,c}' 这将得到 c
  3. 的值

new_value added if create_missing is true ( default is true)

他是文档jsonb -functions

select jsonb_set('{"a":{"b":2}}', '{a,c}','{"a":{"c":3}}'::jsonb#>'{a,c}' )

Result:  {"a":{"c":3,"b":2}}

一次合并更多属性:

with jsonb_paths(main_part,missing_part) as (
values ('{"a":{"b":2}}','{"a":{"c":3,"d":4}}')
)
select jsonb_object_agg(t.k,t.v||t2.v)
from jsonb_paths,
jsonb_each(main_part::jsonb) t(k,v),
jsonb_each(missing_part::jsonb) t2(k,v);

result: {"a":{"c":3,"b":2,"d":4}}

您应该对两个值使用 jsonb_each() 合并未嵌套的元素。在一个重要的查询中这样做可能会让人不舒服,所以我更喜欢这样的自定义函数:

create or replace function jsonb_my_merge(a jsonb, b jsonb)
returns jsonb language sql as $$
    select 
        jsonb_object_agg(
            coalesce(ka, kb), 
            case 
                when va isnull then vb 
                when vb isnull then va 
                else va || vb 
            end
        )
    from jsonb_each(a) e1(ka, va)
    full join jsonb_each(b) e2(kb, vb) on ka = kb
$$;

使用:

select jsonb_my_merge(
    '{"a":{"b":2}, "d": {"e": 10}, "x": 1}'::jsonb, 
    '{"a":{"c":3}, "d": {"f": 11}, "y": 2}'::jsonb
)

                          jsonb_my_merge                          
------------------------------------------------------------------
 {"a": {"b": 2, "c": 3}, "d": {"e": 10, "f": 11}, "x": 1, "y": 2}
(1 row)

您可以使用递归稍微修改函数以获得适用于任何嵌套级别的解决方案:

create or replace function jsonb_recursive_merge(a jsonb, b jsonb)
returns jsonb language sql as $$
    select 
        jsonb_object_agg(
            coalesce(ka, kb), 
            case 
                when va isnull then vb 
                when vb isnull then va 
                when jsonb_typeof(va) <> 'object' then va || vb
                else jsonb_recursive_merge(va, vb)
            end
        )
    from jsonb_each(a) e1(ka, va)
    full join jsonb_each(b) e2(kb, vb) on ka = kb
$$;

示例:

select jsonb_recursive_merge( 
    '{"a":{"b":{"c":3},"x":5}}'::jsonb, 
    '{"a":{"b":{"d":4},"y":6}}'::jsonb);

             jsonb_recursive_merge              
------------------------------------------------
 {"a": {"b": {"c": 3, "d": 4}, "x": 5, "y": 6}}
(1 row)

select jsonb_recursive_merge(
    '{"a":{"b":{"c":{"d":{"e":1}}}}}'::jsonb, 
    '{"a":{"b":{"c":{"d":{"f":2}}}}}'::jsonb)

            jsonb_recursive_merge             
----------------------------------------------
 {"a": {"b": {"c": {"d": {"e": 1, "f": 2}}}}}
(1 row)

最后,具有 OP 提出的更改的函数变体(见下面的评论):

create or replace function jsonb_recursive_merge(a jsonb, b jsonb) 
returns jsonb language sql as $$ 
select 
    jsonb_object_agg(
        coalesce(ka, kb), 
        case 
            when va isnull then vb 
            when vb isnull then va 
            when jsonb_typeof(va) <> 'object' or jsonb_typeof(vb) <> 'object' then vb 
            else jsonb_recursive_merge(va, vb) end 
        ) 
    from jsonb_each(a) e1(ka, va) 
    full join jsonb_each(b) e2(kb, vb) on ka = kb 
$$;

这种 "deep merge" 的解释可能完全不同,具体取决于您的用例。为了完整起见,我的直觉通常会规定以下规则:

  • object + object:每个属性从每个对象中幸存下来,不在另一个对象中(JSON的null value 被认为是 in 对象,如果它被明确提及)。当一个 属性 在两个对象中时,合并以相同的规则递归继续(这一点通常是一致的)。
  • array + array: 结果是两个数组的串联。
  • array + primitive/object:结果是第一个数组,附加了第二个JSON值。
  • 任何其他情况:结果是第二个JSON值(因此f.ex。原语或不兼容类型相互覆盖)。

create or replace function jsonb_merge_deep(jsonb, jsonb)
  returns jsonb
  language sql
  immutable
as $func$
  select case jsonb_typeof()
    when 'object' then case jsonb_typeof()
      when 'object' then (
        select    jsonb_object_agg(k, case
                    when e2.v is null then e1.v
                    when e1.v is null then e2.v
                    else jsonb_merge_deep(e1.v, e2.v)
                  end)
        from      jsonb_each() e1(k, v)
        full join jsonb_each() e2(k, v) using (k)
      )
      else 
    end
    when 'array' then  || 
    else 
  end
$func$;

这个函数的额外好处是它可以用任何类型的 JSON 值调用:总是产生一个结果并且从不抱怨 JSON 值类型。

http://rextester.com/FAC95623

正如@lightSouls 所说,在 PostgreSQL 9.5 之后你可以使用 jsonb_set() 函数......但是你必须学习如何使用它!

jsonb_set 可以合并或销毁...

假设j:='{"a":{"x":1},"b":2}'::jsonb.

  • jsonb_set(j, '{a,y}', '1'::jsonb); 合并 对象 {"y":1} 与对象 {"x":1}
    结果:{"a": {"x": 1, "y": 1}, "b": 2}

  • jsonb_set(j, '{a}', '{"x":1}'::jsonb);毁灭!用新对象替换完整的旧对象。
    结果:{"a": {"x": 1}, "b": 2}