web-dev-qa-db-fra.com

PostgreSQL: Supprimer l'attribut de la colonne JSON

J'ai besoin de supprimer certains attributs d'une colonne de type JSON.

La table:

CREATE TABLE my_table( id VARCHAR(80), data json);
INSERT INTO my_table (id, data) VALUES (
  'A', 
  '{"attrA":1,"attrB":true,"attrC":["a", "b", "c"]}'
);

Maintenant, je dois supprimer attrB de la colonne data.

Quelque chose comme alter table my_table drop column data->'attrB'; serait bien. Mais un moyen avec une table temporaire serait également suffisant.

35
sja

Update: pour la version 9.5+, il existe des opérateurs explicites que vous pouvez utiliser avec jsonb (si vous avez une colonne de type json, vous pouvez utiliser des conversions pour appliquer une modification):

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, au plus profond 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().

Connexe: autres manipulations JSON dans PostgreSQL:

Réponse originale (s'applique à PostgreSQL 9.3):

Si vous avez au moins PostgreSQL 9.3, vous pouvez scinder votre objet en paires avec json_each() et filtrer vos champs non désirés, puis reconstituer manuellement le json. Quelque chose comme:

SELECT data::text::json AS before,
       ('{' || array_to_string(array_agg(to_json(l.key) || ':' || l.value), ',') || '}')::json AS after
FROM (VALUES ('{"attrA":1,"attrB":true,"attrC":["a","b","c"]}'::json)) AS v(data),
LATERAL (SELECT * FROM json_each(data) WHERE "key" <> 'attrB') AS l
GROUP BY data::text

Avec 9.2 (ou moins), ce n'est pas possible.

Modifier:

Une forme plus pratique consiste à créer une fonction pouvant supprimer un nombre quelconque d'attributs dans un champ json:

Edit 2: string_agg() est moins cher que array_to_string(array_agg())

CREATE OR REPLACE FUNCTION "json_object_delete_keys"("json" json, VARIADIC "keys_to_delete" TEXT[])
  RETURNS json
  LANGUAGE sql
  IMMUTABLE
  STRICT
AS $function$
SELECT COALESCE(
  (SELECT ('{' || string_agg(to_json("key") || ':' || "value", ',') || '}')
   FROM json_each("json")
   WHERE "key" <> ALL ("keys_to_delete")),
  '{}'
)::json
$function$;

Avec cette fonction, tout ce que vous avez à faire est d’exécuter la requête ci-dessous:

UPDATE my_table
SET data = json_object_delete_keys(data, 'attrB');
54
pozs

Cela est devenu beaucoup plus facile avec PostgreSQL 9.5 en utilisant le type JSONB. Voir les opérateurs JSONB documentés ici .

Vous pouvez supprimer un attribut de niveau supérieur avec l'opérateur "-".

SELECT '{"a": {"key":"value"}, "b": 2, "c": true}'::jsonb - 'a'
// -> {"b": 2, "c": true}

Vous pouvez l'utiliser dans un appel de mise à jour pour mettre à jour un champ JSONB existant. 

UPDATE my_table SET data = data - 'attrB'

Vous pouvez également fournir le nom de l'attribut de manière dynamique via un paramètre s'il est utilisé dans une fonction.

CREATE OR REPLACE FUNCTION delete_mytable_data_key(
    _id integer,
    _key character varying)
  RETURNS void AS
$BODY$
BEGIN
    UPDATE my_table SET
        data = data - _key
    WHERE id = _id;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

L'opérateur inverse est le "||", afin de concaténer deux paquets JSONB. Notez que l'utilisation la plus à droite de l'attribut écrasera toutes les précédentes.

SELECT '{"a": true, "c": true}'::jsonb || '{"a": false, "b": 2}'::jsonb 
// -> {"a": false, "b": 2, "c": true}
31
mujimu

C'est un hack moche, mais si attrB n'est pas votre première clé et qu'elle n'apparaît qu'une fois, vous pouvez alors:

UPDATE my_table SET data = REPLACE(data::text, ',"attrB":' || (data->'attrB')::text, '')::json;
3
KARASZI István

Je ne pouvais pas obtenir SELECT '{"a": "b"}'::jsonb - 'a'; dans 9.5.2. Cependant, SELECT '{"a": "b"}'::jsonb #- '{a}'; a fonctionné!

2
Westy92

Un autre moyen pratique consiste à utiliser l'extension hstore. De cette façon, vous pouvez écrire une fonction plus pratique pour définir/supprimer des clés dans un objet json. Je suis venu avec la fonction suivante pour faire la même chose:

CREATE OR REPLACE FUNCTION remove_key(json_in json, key_name text)
 RETURNS json AS $$
 DECLARE item json;
 DECLARE fields hstore;
BEGIN
 -- Initialize the hstore with desired key being set to NULL
 fields := hstore(key_name,NULL);

 -- Parse through Input Json and Push each key into hstore 
 FOR item IN  SELECT row_to_json(r.*) FROM json_each_text(json_in) AS r
 LOOP
   --RAISE NOTICE 'Parsing Item % %', item->>'key', item->>'value';
   fields := (fields::hstore || hstore(item->>'key', item->>'value'));
 END LOOP;
 --RAISE NOTICE 'Result %', hstore_to_json(fields);
 -- Remove the desired key from store
 fields := fields-key_name;

 RETURN hstore_to_json(fields);
END;
$$ LANGUAGE plpgsql
SECURITY DEFINER
STRICT;

Un exemple simple d'utilisation est:

SELECT remove_key(('{"Name":"My Name", "Items" :[{ "Id" : 1, "Name" : "Name 1"}, { "Id" : 2, "Name 2" : "Item2 Name"}]}')::json, 'Name');
-- Result
"{"Items": "[{ \"Id\" : 1, \"Name\" : \"Name 1\"}, { \"Id\" : 2, \"Name 2\" : \"Item2 Name\"}]"}"

J'ai une autre fonction pour effectuer l'opération set_key ainsi que les suivantes:

CREATE OR REPLACE FUNCTION set_key(json_in json, key_name text, key_value text)
RETURNS json AS $$
DECLARE item json;
DECLARE fields hstore;
BEGIN
 -- Initialize the hstore with desired key value
 fields := hstore(key_name,key_value);

 -- Parse through Input Json and Push each key into hstore 
 FOR item IN  SELECT row_to_json(r.*) FROM json_each_text(json_in) AS r
 LOOP
   --RAISE NOTICE 'Parsing Item % %', item->>'key', item->>'value';
   fields := (fields::hstore || hstore(item->>'key', item->>'value'));
 END LOOP;
 --RAISE NOTICE 'Result %', hstore_to_json(fields);
 RETURN hstore_to_json(fields);
END;
$$ LANGUAGE plpgsql
SECURITY DEFINER
STRICT;

J'ai discuté de cela plus dans mon blog ici.

1
Sumit Chawla

Bien que cela soit certainement plus facile avec les opérateurs jsonb dans la version 9.5+, la fonction que pozs a écrite pour supprimer plusieurs clés est toujours utile. Par exemple, si les clés à supprimer sont stockées dans une table, vous pouvez utiliser la fonction pour toutes les supprimer. Voici une fonction mise à jour, utilisant jsonb et postgresql 9.5+:

CREATE FUNCTION remove_multiple_keys(IN object jsonb, 
                                     variadic keys_to_delete text[], 
                                     OUT jsonb)
  IMMUTABLE
  STRICT
  LANGUAGE SQL
AS 
$$
  SELECT jsonb_object_agg(key, value)
     FROM (SELECT key, value 
           FROM jsonb_each("object")
           WHERE NOT (key = ANY("keys_to_delete")) 
     ) each_subselect
$$
;

Si les clés à supprimer sont stockées dans une table (par exemple, dans la colonne "clés" de la table "table_avec_codes"), vous pouvez appeler cette fonction de la manière suivante:

SELECT remove_multiple_keys(my_json_object, 
                            VARIADIC (SELECT array_agg(keys) FROM table_with_keys));
0
Jeremy