Merging JSONB values in PostgreSQL?

后端 未结 3 949
生来不讨喜
生来不讨喜 2020-12-02 01:51

Using the || operator yields the following result:

select \'{\"a\":{\"b\":2}}\'::jsonb || \'{\"a\":{\"c\":3}}\'::jsonb ;
    ?column?     
-----         


        
3条回答
  •  孤街浪徒
    2020-12-02 02:25

    You should merge unnested elements using jsonb_each() for both values. Doing this in a non-trivial query may be uncomfortable, so I would prefer a custom function like this one:

    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
    $$;
    

    Use:

    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)
    

    You can slightly modify the function using recursion to get a solution working on any level of nesting:

    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
    $$;
    

    Examples:

    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)
    

    Finally, the variant of the function with changes proposed by OP (see comments below):

    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 
    $$;
    

提交回复
热议问题