6 votes

Fusionner des valeurs JSONB dans PostgreSQL ?

Utilisation de la || donne le résultat suivant :

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

Je souhaiterais pouvoir obtenir le résultat suivant ( ?? juste un substitut pour l'opérateur) :

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

Ainsi, vous pouvez voir le niveau supérieur a a ses valeurs enfant "fusionnées" de telle sorte que le résultat contient à la fois b y c .

Comment fusionner "profondément" deux JSONB dans Postgres ?

Est-ce possible, et si oui, comment ?


Un cas de test plus complexe :

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)

Un autre cas de test où une primitive "fusionne" avec un objet :

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

6voto

klin Points 52538

Vous devez fusionner les éléments non imbriqués en utilisant jsonb_each() pour les deux valeurs. Faire cela dans une requête non triviale peut être inconfortable, je préférerais donc une fonction personnalisée comme celle-ci :

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

Utilisez :

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)

Vous pouvez modifier légèrement la fonction en utilisant la récursion pour obtenir une solution fonctionnant à n'importe quel niveau d'imbrication :

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

Exemples :

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)

Enfin, la variante de la fonction avec les changements proposés par le PO (voir les commentaires ci-dessous) :

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

5voto

pozs Points 6034

Ce type de "fusion profonde" peut être interprété de manière très différente, en fonction de votre cas d'utilisation. Pour être complet, mon intuition me dicte généralement les règles suivantes :

  • objet + objet : Chaque propriété survit de chaque objet, qui n'est pas dans l'autre objet (JSON's null est considérée comme sur l'objet, s'il est explicitement mentionné). Lorsqu'une propriété se trouve dans les deux objets, la fusion se poursuit récursivement avec les mêmes règles (ce point fait généralement l'objet d'un accord).
  • tableau + tableau : Le résultat est la concaténation des deux tableaux.
  • tableau + primitif/objet le résultat est le premier tableau, auquel est ajoutée la deuxième valeur JSON.
  • tout autre cas : Le résultat est la deuxième valeur JSON (ainsi, par exemple, les primitives ou les types incompatibles se substituent les uns aux autres).

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

Le bonus supplémentaire de cette fonction est qu'elle peut être appelée avec littéralement n'importe quel type de valeurs JSON : elle produit toujours un résultat et ne se plaint jamais des types de valeurs JSON.

http://rextester.com/FAC95623

1voto

light souls Points 434

Après PostgreSQL 9.5 vous pouvez utiliser jsonb_set fonction :

  1. '{a,c}' en regardant dans le chemin s'il n'est pas là, il sera créé.
  2. '{"a":{"c":3}}'::jsonb#>'{a,c}' cela permettra d'obtenir la valeur de c

nouvelle_valeur ajoutée si create_missing est vrai ( par défaut c'est vrai)

Voici le document jsonb -functions

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

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

Fusionner plusieurs attributs à la fois :

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}}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X