PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comment modifier les champs à l'intérieur du nouveau type de données PostgreSQL JSON ?

Mettre à jour :Avec PostgreSQL 9.5, il y a du jsonb fonctionnalité de manipulation dans PostgreSQL lui-même (mais aucune pour json; les casts sont nécessaires pour manipuler json valeurs).

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]'

Donc, définir une clé simple peut être fait en utilisant :

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

<key> doit être une chaîne et <value> peut être n'importe quel type to_jsonb() accepte.

Pour définir une valeur profondément dans une hiérarchie JSON , le jsonb_set() fonction 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 comptent à partir de la fin des tableaux JSON. Cependant, un index de tableau JSON inexistant 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 le tableau JSON (tout en préservant toutes les valeurs d'origine) , le jsonb_insert() la fonction 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)

Encore une fois, les entiers négatifs qui apparaissent dans path compter à partir de la fin des tableaux JSON.

Donc, par ex. l'ajout à la fin d'un tableau JSON peut être fait 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 (que jsonb_set() ) lorsque le path dans target est la clé d'un objet JSON. Dans ce cas, il n'ajoutera une nouvelle paire clé-valeur pour l'objet JSON que lorsque la clé n'est pas utilisée. S'il est utilisé, il déclenchera 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

Supprimer une clé (ou un index) à partir d'un objet JSON (ou, à partir d'un tableau) peut être fait avec le - 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]'

Suppression, depuis le plus profond d'une hiérarchie JSON peut être fait avec le #- 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 agréger dans un objet json directement 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$;

Modifier 2  :comme @ErwinBrandstetter l'a noté, ces fonctions ci-dessus fonctionnent comme un soi-disant UPSERT (met à jour un champ s'il existe, insère s'il n'existe pas). Voici une variante, qui ne fait que 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$;

Modifier 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 dans un chemin de clé (où les clés ne peuvent faire référence qu'à des objets internes, les tableaux internes ne sont pas pris en charge) :

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

Mise à jour :Ajout d'une fonction pour remplacer la clé d'un champ json existant par une autre clé donnée. Peut être utile pour mettre à jour les types de données dans les migrations ou d'autres scénarios tels que la modification de la structure des données.

CREATE OR REPLACE FUNCTION json_object_replace_key(
    json_value json,
    existing_key text,
    desired_key text)
  RETURNS json AS
$BODY$
SELECT COALESCE(
(
    SELECT ('{' || string_agg(to_json(key) || ':' || value, ',') || '}')
    FROM (
        SELECT *
        FROM json_each(json_value)
        WHERE key <> existing_key
        UNION ALL
        SELECT desired_key, json_value -> existing_key
    ) AS "fields"
    -- WHERE value IS NOT NULL (Actually not required as the string_agg with value's being null will "discard" that entry)

),
    '{}'
)::json
$BODY$
  LANGUAGE sql IMMUTABLE STRICT
  COST 100;

Mettre à jour  :les fonctions sont maintenant compactées.