web-dev-qa-db-fra.com

Comment modifier des champs dans le nouveau type de données PostgreSQL JSON?

Avec postgresql 9.3, je peux sélectionner des champs spécifiques d'un type de données JSON, mais comment les modifier à l'aide de UPDATE? Je ne trouve aucun exemple dans la documentation postgresql, ni ailleurs en ligne. J'ai essayé l'évidence:

postgres=# create table test (data json);
CREATE TABLE
postgres=# insert into test (data) values ('{"a":1,"b":2}');
INSERT 0 1
postgres=# select data->'a' from test where data->>'b' = '2';
 ?column?
----------
 1
(1 row)
postgres=# update test set data->'a' = to_json(5) where data->>'b' = '2';
ERROR:  syntax error at or near "->"
LINE 1: update test set data->'a' = to_json(5) where data->>'b' = '2...
167
user9645

Update: Avec PostgreSQL 9.5 , il existe quelques fonctionnalités de manipulation jsonb dans PostgreSQL (mais aucune pour json; des transformations sont nécessaires pour manipuler les valeurs json.).

Fusion de 2 (ou plus) objets JSON (ou concaténation de tableaux):

SELECT jsonb '{"a":1}' || jsonb '{"b":2}', -- will yield jsonb '{"a":1,"b":2}'
       jsonb '["a",1]' || jsonb '["b",2]'  -- will yield jsonb '["a",1,"b",2]'

Ainsi, vous pouvez définir une clé simple en utilisant:

SELECT jsonb '{"a":1}' || jsonb_build_object('<key>', '<value>')

<key> doit être une chaîne et <value> peut être de type to_jsonb() accepté.

Pour définir une valeur profonde dans une hiérarchie JSON, la fonction jsonb_set() peut être utilisée:

SELECT jsonb_set('{"a":[null,{"b":[]}]}', '{a,1,b,0}', jsonb '{"c":3}')
-- will yield jsonb '{"a":[null,{"b":[{"c":3}]}]}'

Liste complète des paramètres de jsonb_set():

jsonb_set(target         jsonb,
          path           text[],
          new_value      jsonb,
          create_missing boolean default true)

path peut également contenir des index de tableau JSON et les entiers négatifs qui y apparaissent à partir de la fin des tableaux JSON. Cependant, un index de tableau JSON non existant mais positif ajoutera l'élément à la fin du tableau:

SELECT jsonb_set('{"a":[null,{"b":[1,2]}]}', '{a,1,b,1000}', jsonb '3', true)
-- will yield jsonb '{"a":[null,{"b":[1,2,3]}]}'

Pour insérer dans un tableau JSON (tout en préservant toutes les valeurs d'origine), la fonction jsonb_insert() peut être utilisée (dans 9.6+; cette fonction uniquement, dans cette section):

SELECT jsonb_insert('{"a":[null,{"b":[1]}]}', '{a,1,b,0}', jsonb '2')
-- will yield jsonb '{"a":[null,{"b":[2,1]}]}', and
SELECT jsonb_insert('{"a":[null,{"b":[1]}]}', '{a,1,b,0}', jsonb '2', true)
-- will yield jsonb '{"a":[null,{"b":[1,2]}]}'

Liste complète des paramètres de jsonb_insert():

jsonb_insert(target       jsonb,
             path         text[],
             new_value    jsonb,
             insert_after boolean default false)

De nouveau, les entiers négatifs apparaissant dans path comptent à partir de la fin des tableaux JSON.

Donc, f.ex. L’ajout à la fin d’un tableau JSON peut être effectué avec:

SELECT jsonb_insert('{"a":[null,{"b":[1,2]}]}', '{a,1,b,-1}', jsonb '3', true)
-- will yield jsonb '{"a":[null,{"b":[1,2,3]}]}', and

Cependant, cette fonction fonctionne légèrement différemment (par rapport à jsonb_set()) lorsque path dans target est la clé d'un objet JSON. Dans ce cas, une nouvelle paire clé-valeur n'est ajoutée pour l'objet JSON que lorsque la clé n'est pas utilisée. S'il est utilisé, cela provoquera une erreur:

SELECT jsonb_insert('{"a":[null,{"b":[1]}]}', '{a,1,c}', jsonb '[2]')
-- will yield jsonb '{"a":[null,{"b":[1],"c":[2]}]}', but
SELECT jsonb_insert('{"a":[null,{"b":[1]}]}', '{a,1,b}', jsonb '[2]')
-- will raise SQLSTATE 22023 (invalid_parameter_value): cannot replace existing key

La suppression d'une clé (ou d'un index) d'un objet JSON (ou d'un tableau) peut être effectuée à l'aide de l'opérateur -:

SELECT jsonb '{"a":1,"b":2}' - 'a', -- will yield jsonb '{"b":2}'
       jsonb '["a",1,"b",2]' - 1    -- will yield jsonb '["a","b",2]'

La suppression depuis le fond d'une hiérarchie JSON peut être effectuée à l'aide de l'opérateur #-:

SELECT '{"a":[null,{"b":[3.14]}]}' #- '{a,1,b,0}'
-- will yield jsonb '{"a":[null,{"b":[]}]}'

Pour 9.4, vous pouvez utiliser une version modifiée de la réponse d'origine (ci-dessous), mais au lieu d'agréger une chaîne JSON, vous pouvez l'agréger directement dans un objet JSON avec json_object_agg().

Réponse originale: C'est possible (sans plpython ou plv8) en SQL pur aussi (mais nécessite 9.3+, ne fonctionnera pas avec 9.2)

CREATE OR REPLACE FUNCTION "json_object_set_key"(
  "json"          json,
  "key_to_set"    TEXT,
  "value_to_set"  anyelement
)
  RETURNS json
  LANGUAGE sql
  IMMUTABLE
  STRICT
AS $function$
SELECT concat('{', string_agg(to_json("key") || ':' || "value", ','), '}')::json
  FROM (SELECT *
          FROM json_each("json")
         WHERE "key" <> "key_to_set"
         UNION ALL
        SELECT "key_to_set", to_json("value_to_set")) AS "fields"
$function$;

SQLFiddle

Modifier:

Une version qui définit plusieurs clés et valeurs:

CREATE OR REPLACE FUNCTION "json_object_set_keys"(
  "json"          json,
  "keys_to_set"   TEXT[],
  "values_to_set" anyarray
)
  RETURNS json
  LANGUAGE sql
  IMMUTABLE
  STRICT
AS $function$
SELECT concat('{', string_agg(to_json("key") || ':' || "value", ','), '}')::json
  FROM (SELECT *
          FROM json_each("json")
         WHERE "key" <> ALL ("keys_to_set")
         UNION ALL
        SELECT DISTINCT ON ("keys_to_set"["index"])
               "keys_to_set"["index"],
               CASE
                 WHEN "values_to_set"["index"] IS NULL THEN 'null'::json
                 ELSE to_json("values_to_set"["index"])
               END
          FROM generate_subscripts("keys_to_set", 1) AS "keys"("index")
          JOIN generate_subscripts("values_to_set", 1) AS "values"("index")
         USING ("index")) AS "fields"
$function$;

Edit 2: as @ErwinBrandstetter noté ces fonctions ci-dessus fonctionnent comme ce que l’on appelle UPSERT (met à jour un champ s’il existe, insère s’il n’existe pas). Voici une variante, dont seulement UPDATE:

CREATE OR REPLACE FUNCTION "json_object_update_key"(
  "json"          json,
  "key_to_set"    TEXT,
  "value_to_set"  anyelement
)
  RETURNS json
  LANGUAGE sql
  IMMUTABLE
  STRICT
AS $function$
SELECT CASE
  WHEN ("json" -> "key_to_set") IS NULL THEN "json"
  ELSE (SELECT concat('{', string_agg(to_json("key") || ':' || "value", ','), '}')
          FROM (SELECT *
                  FROM json_each("json")
                 WHERE "key" <> "key_to_set"
                 UNION ALL
                SELECT "key_to_set", to_json("value_to_set")) AS "fields")::json
END
$function$;

Edit 3: Voici une variante récursive qui peut définir (UPSERT) une valeur feuille (et utilise la première fonction de cette réponse), située sur un chemin de clé (où les clés ne peuvent se rapporter qu'à des objets internes, des tableaux internes). non supporté):

CREATE OR REPLACE FUNCTION "json_object_set_path"(
  "json"          json,
  "key_path"      TEXT[],
  "value_to_set"  anyelement
)
  RETURNS json
  LANGUAGE sql
  IMMUTABLE
  STRICT
AS $function$
SELECT CASE COALESCE(array_length("key_path", 1), 0)
         WHEN 0 THEN to_json("value_to_set")
         WHEN 1 THEN "json_object_set_key"("json", "key_path"[l], "value_to_set")
         ELSE "json_object_set_key"(
           "json",
           "key_path"[l],
           "json_object_set_path"(
             COALESCE(NULLIF(("json" -> "key_path"[l])::text, 'null'), '{}')::json,
             "key_path"[l+1:u],
             "value_to_set"
           )
         )
       END
  FROM array_lower("key_path", 1) l,
       array_upper("key_path", 1) u
$function$;

Update: les fonctions sont maintenant compactées.

249
pozs

Avec 9.5, utilisez jsonb_set-

UPDATE objects
SET body = jsonb_set(body, '{name}', '"Mary"', true)
WHERE id = 1; 

où body est un type de colonne jsonb.

61
Teo Choong Ping

Avec Postgresql 9.5, vous pouvez le faire comme suit:

UPDATE test
SET data = data - 'a' || '{"a":5}'
WHERE data->>'b' = '2';

OR

UPDATE test
SET data = jsonb_set(data, '{a}', '5'::jsonb);

Quelqu'un a demandé comment mettre à jour plusieurs champs de la valeur jsonb à la fois. Supposons que nous créons une table:

CREATE TABLE testjsonb ( id SERIAL PRIMARY KEY, object JSONB );

Puis on INSERE une ligne expérimentale:

INSERT INTO testjsonb
VALUES (DEFAULT, '{"a":"one", "b":"two", "c":{"c1":"see1","c2":"see2","c3":"see3"}}');

Ensuite, nous mettons à jour la ligne:

UPDATE testjsonb SET object = object - 'b' || '{"a":1,"d":4}';

Qui fait ce qui suit:

  1. Met à jour le champ
  2. Supprime le champ b
  3. Ajouter le champ d

Sélection des données:

SELECT jsonb_pretty(object) FROM testjsonb;

Aura pour résultat:

      jsonb_pretty
-------------------------
 {                      +
     "a": 1,            +
     "c": {             +
         "c1": "see1",  +
         "c2": "see2",  +
         "c3": "see3",  +
     },                 +
     "d": 4             +
 }
(1 row)

Pour mettre à jour le champ à l'intérieur, n'utilisez pas l'opérateur de concaternation ||. Utilisez plutôt jsonb_set. Ce qui n'est pas simple:

UPDATE testjsonb SET object =
jsonb_set(jsonb_set(object, '{c,c1}','"seeme"'),'{c,c2}','"seehim"');

En utilisant l'opérateur concat pour {c, c1} par exemple:

UPDATE testjsonb SET object = object || '{"c":{"c1":"seedoctor"}}';

Supprime {c, c2} et {c, c3}.

Pour plus de puissance, recherchez le pouvoir dans la documentation sur les fonctions json postgresql . On pourrait être intéressé par l'opérateur #-, la fonction jsonb_set et aussi la fonction jsonb_insert.

29
Fandi Susanto

Pour compléter les réponses de @ pozs, voici quelques autres fonctions PostgreSQL qui pourraient être utiles à certains. (Requiert PostgreSQL 9.3+)

Supprimer par clé: Supprime une valeur de la structure JSON par clé.

CREATE OR REPLACE FUNCTION "json_object_del_key"(
  "json"          json,
  "key_to_del"    TEXT
)
  RETURNS json
  LANGUAGE sql
  IMMUTABLE
  STRICT
AS $function$
SELECT CASE
  WHEN ("json" -> "key_to_del") IS NULL THEN "json"
  ELSE (SELECT concat('{', string_agg(to_json("key") || ':' || "value", ','), '}')
          FROM (SELECT *
                  FROM json_each("json")
                 WHERE "key" <> "key_to_del"
               ) AS "fields")::json
END
$function$;

Suppression récursive par clé: Supprime une valeur de la structure JSON par chemin de clé. (requiert la fonction json_object_set_key de @ pozs)

CREATE OR REPLACE FUNCTION "json_object_del_path"(
  "json"          json,
  "key_path"      TEXT[]
)
  RETURNS json
  LANGUAGE sql
  IMMUTABLE
  STRICT
AS $function$
SELECT CASE
  WHEN ("json" -> "key_path"[l] ) IS NULL THEN "json"
  ELSE
     CASE COALESCE(array_length("key_path", 1), 0)
         WHEN 0 THEN "json"
         WHEN 1 THEN "json_object_del_key"("json", "key_path"[l])
         ELSE "json_object_set_key"(
           "json",
           "key_path"[l],
           "json_object_del_path"(
             COALESCE(NULLIF(("json" -> "key_path"[l])::text, 'null'), '{}')::json,
             "key_path"[l+1:u]
           )
         )
       END
    END
  FROM array_lower("key_path", 1) l,
       array_upper("key_path", 1) u
$function$;

Exemples d'utilisation:

s1=# SELECT json_object_del_key ('{"hello":[7,3,1],"foo":{"mofu":"fuwa", "moe":"kyun"}}',
                                 'foo'),
            json_object_del_path('{"hello":[7,3,1],"foo":{"mofu":"fuwa", "moe":"kyun"}}',
                                 '{"foo","moe"}');

 json_object_del_key |          json_object_del_path
---------------------+-----------------------------------------
 {"hello":[7,3,1]}   | {"hello":[7,3,1],"foo":{"mofu":"fuwa"}}
9
shru
UPDATE test
SET data = data::jsonb - 'a' || '{"a":5}'::jsonb
WHERE data->>'b' = '2'

Cela semble fonctionner sur PostgreSQL 9.5

8
sigod

Avec PostgreSQL 9.4, nous avons implémenté la fonction python suivante. Cela peut aussi fonctionner avec PostgreSQL 9.3.

create language plpython2u;

create or replace function json_set(jdata jsonb, jpaths jsonb, jvalue jsonb) returns jsonb as $$
import json

a = json.loads(jdata)
b = json.loads(jpaths)

if a.__class__.__!= 'dict' and a.__class__.__!= 'list':
  raise plpy.Error("The json data must be an object or a string.")

if b.__class__.__!= 'list':
   raise plpy.Error("The json path must be an array of paths to traverse.")

c = a
for i in range(0, len(b)):
  p = b[i]
  plpy.notice('p == ' + str(p))

  if i == len(b) - 1:
    c[p] = json.loads(jvalue)

  else:
    if p.__class__.__== 'unicode':
      plpy.notice("Traversing '" + p + "'")
      if c.__class__.__!= 'dict':
        raise plpy.Error("  The value here is not a dictionary.")
      else:
        c = c[p]

    if p.__class__.__== 'int':
      plpy.notice("Traversing " + str(p))
      if c.__class__.__!= 'list':
        raise plpy.Error("  The value here is not a list.")
      else:
        c = c[p]

    if c is None:
      break    

return json.dumps(a)
$$ language plpython2u ;

Exemple d'utilisation:

create table jsonb_table (jsonb_column jsonb);
insert into jsonb_table values
('{"cars":["Jaguar", {"type":"Unknown","partsList":[12, 34, 56]}, "Atom"]}');

select jsonb_column->'cars'->1->'partsList'->2, jsonb_column from jsonb_table;

update jsonb_table
set jsonb_column = json_set(jsonb_column, '["cars",1,"partsList",2]', '99');

select jsonb_column->'cars'->1->'partsList'->2, jsonb_column from jsonb_table;

Notez que pour un employeur précédent, j'ai écrit un ensemble de fonctions C pour manipuler les données JSON sous forme de texte (et non de type json ou jsonb) pour PostgreSQL 7, 8 et 9. Par exemple, extraire des données avec json_path('{"obj":[12, 34, {"num":-45.67}]}', '$.obj[2]['num']'), définir des données avec json_path_set('{"obj":[12, 34, {"num":-45.67}]}', '$.obj[2]['num']', '99.87') etc. Cela a pris environ 3 jours de travail, donc si vous en avez besoin pour fonctionner sur des systèmes existants et que vous avez du temps à perdre, cela en vaudra peut-être la peine. J'imagine que la version C est beaucoup plus rapide que la version python.

4
Magnus

Même si les éléments suivants ne satisferont pas cette demande (la fonction json_object_agg n’est pas disponible dans PostgreSQL 9.3), les éléments suivants peuvent être utiles à quiconque recherche un || opérateur pour PostgreSQL 9.4, tel qu’implémenté dans la prochaine version de PostgreSQL 9.5:

CREATE OR REPLACE FUNCTION jsonb_merge(left JSONB, right JSONB)
RETURNS JSONB
AS $$
SELECT
  CASE WHEN jsonb_typeof($1) = 'object' AND jsonb_typeof($2) = 'object' THEN
       (SELECT json_object_agg(COALESCE(o.key, n.key), CASE WHEN n.key IS NOT NULL THEN n.value ELSE o.value END)::jsonb
        FROM jsonb_each($1) o
        FULL JOIN jsonb_each($2) n ON (n.key = o.key))
   ELSE 
     (CASE WHEN jsonb_typeof($1) = 'array' THEN LEFT($1::text, -1) ELSE '['||$1::text END ||', '||
      CASE WHEN jsonb_typeof($2) = 'array' THEN RIGHT($2::text, -1) ELSE $2::text||']' END)::jsonb
   END     
$$ LANGUAGE sql IMMUTABLE STRICT;
GRANT EXECUTE ON FUNCTION jsonb_merge(jsonb, jsonb) TO public;
CREATE OPERATOR || ( LEFTARG = jsonb, RIGHTARG = jsonb, PROCEDURE = jsonb_merge );

J'ai écrit pour moi une petite fonction qui fonctionne de manière récursive dans Postgres 9.4. Voici la fonction (j'espère que cela fonctionne bien pour vous):

CREATE OR REPLACE FUNCTION jsonb_update(val1 JSONB,val2 JSONB)
RETURNS JSONB AS $$
DECLARE
    result JSONB;
    v RECORD;
BEGIN
    IF jsonb_typeof(val2) = 'null'
    THEN 
        RETURN val1;
    END IF;

    result = val1;

    FOR v IN SELECT key, value FROM jsonb_each(val2) LOOP

        IF jsonb_typeof(val2->v.key) = 'object'
            THEN
                result = result || jsonb_build_object(v.key, jsonb_update(val1->v.key, val2->v.key));
            ELSE
                result = result || jsonb_build_object(v.key, v.value);
        END IF;
    END LOOP;

    RETURN result;
END;
$$ LANGUAGE plpgsql;

Voici un exemple d'utilisation:

select jsonb_update('{"a":{"b":{"c":{"d":5,"dd":6},"cc":1}},"aaa":5}'::jsonb, '{"a":{"b":{"c":{"d":15}}},"aa":9}'::jsonb);
                            jsonb_update                             
---------------------------------------------------------------------
 {"a": {"b": {"c": {"d": 15, "dd": 6}, "cc": 1}}, "aa": 9, "aaa": 5}
(1 row)

Comme vous pouvez le voir, analysez en profondeur et mettez à jour/ajoutez des valeurs si nécessaire.

2
J. Raczkiewicz

Si votre type de champ est JSON, ce qui suit fonctionnera pour vous.

UPDATE 
table_name
SET field_name = field_name::jsonb - 'key' || '{"key":new_val}' 
WHERE field_name->>'key' = 'old_value'.

Opérateur '-' supprime la paire clé/valeur ou l’élément de chaîne de l’opérande de gauche. Les paires clé/valeur sont appariées en fonction de leur valeur clé.

Opérateur '||' concaténer deux valeurs jsonb dans une nouvelle valeur jsonb.

Comme il s'agit d'opérateurs jsonb, il vous suffit de dactylographier :: jsonb

Plus d'infos: Fonctions et opérateurs JSON

Vous pouvez lire ma note ici

1
Neethu

L'extrait de plpython suivant peut être utile.

CREATE EXTENSION IF NOT EXISTS plpythonu;
CREATE LANGUAGE plpythonu;

CREATE OR REPLACE FUNCTION json_update(data json, key text, value text)
 RETURNS json
 AS $$
    import json
    json_data = json.loads(data)
    json_data[key] = value
    return json.dumps(json_data, indent=4)
 $$ LANGUAGE plpythonu;

-- Check how JSON looks before updating

SELECT json_update(content::json, 'CFRDiagnosis.mod_nbs', '1')
FROM sc_server_centre_document WHERE record_id = 35 AND template = 'CFRDiagnosis';

-- Once satisfied update JSON inplace

UPDATE sc_server_centre_document SET content = json_update(content::json, 'CFRDiagnosis.mod_nbs', '1')
WHERE record_id = 35 AND template = 'CFRDiagnosis';
1
Sandeep

Malheureusement, je n'ai rien trouvé dans la documentation, mais vous pouvez utiliser une solution de contournement, par exemple, vous pouvez écrire une fonction étendue.

Par exemple, en Python:

CREATE or REPLACE FUNCTION json_update(data json, key text, value json)
returns json
as $$
from json import loads, dumps
if key is None: return data
js = loads(data)
js[key] = value
return dumps(js)
$$ language plpython3u

et alors

update test set data=json_update(data, 'a', to_json(5)) where data->>'b' = '2';
1
Roman Pekar

Pour ceux qui utilisent mybatis, voici un exemple d'instruction de mise à jour:

<update id="saveAnswer">
    update quiz_execution set answer_data = jsonb_set(answer_data, concat('{', #{qid}, '}')::text[], #{value}::jsonb), updated_at = #{updatedAt}
    where id = #{id}
</update>


Params:

  • qid, la clé du champ.
  • value, est une chaîne json valide, pour la valeur du champ,
    Par exemple, converti d'un objet à une chaîne json via jackson,
0
Eric Wang

Cela a fonctionné pour moi lorsque vous essayez de mettre à jour un champ de type chaîne.

UPDATE table_name 
SET body = jsonb_set(body, '{some_key}', to_json('value'::TEXT)::jsonb);

J'espère que ça aide quelqu'un d'autre à sortir!

0
Antonio

Vous pouvez également incrémenter les clés de manière atomique dans jsonb comme ceci:

UPDATE users SET counters = counters || CONCAT('{"bar":', COALESCE(counters->>'bar','0')::int + 1, '}')::jsonb WHERE id = 1;

SELECT * FROM users;

 id |    counters
----+------------
  1 | {"bar": 1}

Clé non définie -> suppose que la valeur de départ est 0.

Pour une explication plus détaillée, voir ma réponse ici: https://stackoverflow.com/a/39076637

0
joonas.fi

J'ai trouvé les réponses précédentes valables pour les utilisateurs expérimentés de PostgreSQL, d'où ma réponse:

Supposons que vous avez une table-colonne de type JSONB avec la valeur suivante:

{
    "key0": {
        "key01": "2018-05-06T12:36:11.916761+00:00",
        "key02": "DEFAULT_WEB_CONFIGURATION",

    "key1": {
        "key11": "Data System",
        "key12": "<p>Health,<p>my address<p>USA",
        "key13": "*Please refer to main screen labeling"
    }
}

supposons que nous voulions définir une nouvelle valeur dans la ligne:

"key13": "*Please refer to main screen labeling"

et placez plutôt la valeur:

"key13": "See main screen labeling"

nous utilisons la fonction json_set () pour assigner une nouvelle valeur à la clé13

les paramètres à jsonb_set ()

jsonb_set(target jsonb, path text[], new_value jsonb[, create_missing boolean])

dans " target " - Je placerai le nom de colonne jsonb (il s'agit de la colonne de la table en cours de modification)

" path " - est le "chemin des clés json" menant à (et incluant) la clé que nous allons écraser

" new_value " - c'est la nouvelle valeur que nous affectons

dans notre cas, nous voulons mettre à jour la valeur de key13 qui se trouve sous key1 (key1 -> key13):

par conséquent, la syntaxe du chemin est la suivante: '{key1, key13}' (le chemin était la partie la plus difficile à résoudre - car les tutoriels sont terribles)

jsonb_set(jsonb_column,'{key1,key13}','"See main screen labeling"')
0
Dror