合并PostgreSQL中的JSONB值?

时间:2017-03-22 07:07:16

标签: json postgresql jsonb

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

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键具有其子值"合并"结果包含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)

另一个测试用例,其中一个原语"合并在一起"和对象:

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

3 个答案:

答案 0 :(得分:7)

您应该使用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 
$$;

答案 1 :(得分:5)

根据您的使用情况,这种“深度合并”的解释可能完全不同。为了完整起见,我的直觉通常规定了以下规则:

  • object + object :每个属性都存在于每个对象中,而不在另一个对象中(JSON的null值被认为是 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($1)
    when 'object' then case jsonb_typeof($2)
      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($1) e1(k, v)
        full join jsonb_each($2) e2(k, v) using (k)
      )
      else $2
    end
    when 'array' then $1 || $2
    else $2
  end
$func$;

这个函数的额外好处是它可以用字面上的任何类型的JSON值调用:总是产生一个结果&amp;永远不要抱怨JSON值类型。

http://rextester.com/FAC95623

答案 2 :(得分:2)

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

  1. '{a,c}'查看路径,如果不存在,则会创建。
  2. '{"a":{"c":3}}'::jsonb#>'{a,c}'这将获得c
  3. 的值
      如果create_missing为true(默认为true),则添加

    new_value

    Hier是文件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}}