web-dev-qa-db-fra.com

Comment effectuer des opérations de mise à jour sur des colonnes de type JSONB dans Postgres 9.4

En parcourant la documentation du type de données JSONB de Postgres 9.4, il n’est pas immédiatement évident de savoir comment effectuer des mises à jour sur des colonnes JSONB.

Documentation sur les types et fonctions JSONB:

http://www.postgresql.org/docs/9.4/static/functions-json.htmlhttp://www.postgresql.org/docs/9.4/static/datatype- json.html

A titre d'exemple, j'ai cette structure de table de base:

CREATE TABLE test(id serial, data jsonb);

L'insertion est facile, comme dans:

INSERT INTO test(data) values ('{"name": "my-name", "tags": ["tag1", "tag2"]}');

Maintenant, comment pourrais-je mettre à jour la colonne 'data'? Ceci est une syntaxe invalide:

UPDATE test SET data->'name' = 'my-other-name' WHERE id = 1;

Est-ce documenté quelque part évident que j'ai manqué? Merci.

102
jvous

Idéalement, vous n'utilisez pas de documents JSON pour les données structurées et régulières que vous souhaitez manipuler dans une base de données relationnelle. Utilisez plutôt un design relationnel normalisé .

JSON est principalement destiné à stocker des documents entiers qui n'ont pas besoin d'être manipulés dans le SGBDR. Apparenté, relié, connexe:

La mise à jour d'une ligne dans Postgres écrit toujours une nouvelle version de la ligne entière . C'est le principe de base de modèle MVCC de Postgres . Du point de vue des performances, peu importe que vous changiez une seule donnée dans un objet JSON ou la totalité de celle-ci: une nouvelle version de la ligne doit être écrite.

Ainsi le conseil dans le manuel :

Les données JSON sont soumises aux mêmes considérations de contrôle de simultanéité que tout autre type de données lorsqu'elles sont stockées dans une table. Bien que le stockage de documents volumineux soit réalisable, n'oubliez pas que toute mise à jour acquiert un verrou au niveau de la ligne sur toute la ligne. Envisagez de limiter la taille des documents JSON afin de réduire les conflits de verrous entre les transactions de mise à jour. Idéalement, les documents JSON doivent représenter chacun un datum atomique dont les règles commerciales dictent qu'il ne peut pas être raisonnablement subdivisé en plus petits datums pouvant être modifiés indépendamment.

L'essentiel: pour modifier quelque chose à l'intérieur d'un objet JSON, vous devez affecter un objet modifié à la colonne. Postgres fournit des moyens limités pour construire et manipuler les données json en plus de ses capacités de stockage. L'arsenal d'outils a considérablement augmenté avec chaque nouvelle version depuis la version 9.2. Mais le principal reste: vous devez toujours assigner un objet complet modifié à la colonne et Postgres écrit toujours une nouvelle version de ligne pour toute mise à jour.

Quelques techniques pour travailler avec les outils de Postgres 9.3 ou version ultérieure:

Cette réponse a attiré à peu près autant de votes négatifs que toutes mes autres réponses sur SO ensemble . Les gens ne semblent pas aimer l'idée: une conception normalisée est préférable pour des données non dynamiques. Cet excellent article de Craig Ringer explique plus en détail:

35

Si vous pouvez passer à Postgresql 9.5, la commande jsonb_set est disponible, comme d'autres l'ont mentionné.

Dans chacune des instructions SQL suivantes, j'ai omis la clause where par souci de brièveté; évidemment, vous voudriez rajouter cela.

Nom de la mise à jour:

UPDATE test SET data = jsonb_set(data, '{name}', '"my-other-name"');

Remplacez les balises (par opposition à l'ajout ou la suppression de balises):

UPDATE test SET data = jsonb_set(data, '{tags}', '["tag3", "tag4"]');

Remplacement de la deuxième balise (index 0):

UPDATE test SET data = jsonb_set(data, '{tags,1}', '"tag5"');

Ajouter une balise (cela fonctionnera tant qu'il y aura moins de 999 tags; changer l'argument 999 en 1000 ou plus génère une erreur. Cela ne semble plus être le cas dans Postgres 9.5.3; un indice beaucoup plus grand peut être utilisé):

UPDATE test SET data = jsonb_set(data, '{tags,999999999}', '"tag6"', true);

Supprimer la dernière balise:

UPDATE test SET data = data #- '{tags,-1}'

Mise à jour complexe (supprimer la dernière balise, insérer une nouvelle balise et changer le nom):

UPDATE test SET data = jsonb_set(
    jsonb_set(data #- '{tags,-1}', '{tags,999999999}', '"tag3"', true), 
    '{name}', '"my-other-name"');

Il est important de noter que dans chacun de ces exemples, vous ne mettez pas à jour un seul champ des données JSON. Au lieu de cela, vous créez une version temporaire modifiée des données et assignez cette version modifiée à la colonne. En pratique, le résultat devrait être identique, mais garder cela à l'esprit devrait rendre les mises à jour complexes, comme le dernier exemple, plus compréhensibles.

Dans l'exemple complexe, il existe trois transformations et trois versions temporaires: Tout d'abord, la dernière balise est supprimée. Ensuite, cette version est transformée en ajoutant une nouvelle balise. Ensuite, la deuxième version est transformée en modifiant le champ name. La valeur dans la colonne data est remplacée par la version finale.

223
Jimothy

Cela arrive dans 9.5 sous la forme de jsonb_set par Andrew Dunstan basé sur une extension existante jsonbx qui fonctionne avec 9.4

23
philofinfinitejest

Pour ceux qui rencontrent ce problème et veulent une solution rapide (et qui sont bloqués sur 9.4.5 ou une version antérieure), voici ce que j'ai fait:

Création de la table de test

CREATE TABLE test(id serial, data jsonb);
INSERT INTO test(data) values ('{"name": "my-name", "tags": ["tag1", "tag2"]}');

Instruction de mise à jour pour changer le nom de la propriété jsonb

UPDATE test 
SET data = replace(data::TEXT,'"name":','"my-other-name":')::jsonb 
WHERE id = 1;

En fin de compte, la réponse acceptée est correcte dans la mesure où vous ne pouvez pas modifier un élément individuel d'un objet jsonb (dans 9.4.5 ou une version antérieure); Cependant, vous pouvez convertir l'objet jsonb en une chaîne (:: TEXT), puis la manipuler et la reconvertir en objet jsonb (:: jsonb).

Il y a deux mises en garde importantes

  1. cela remplacera toutes les propriétés appelées "name" dans le JSON (dans le cas où vous avez plusieurs propriétés portant le même nom)
  2. ce n'est pas aussi efficace que jsonb_set serait si vous utilisez 9.5

Cela dit, je me suis trouvé dans une situation où je devais mettre à jour le schéma du contenu des objets jsonb. C’était le moyen le plus simple d’accomplir exactement ce que l’affiche originale demandait.

14
Chad Capra

Cette question a été posée dans le contexte de postgres 9.4. Toutefois, les nouveaux lecteurs qui l'utilisent doivent savoir que dans Postgres 9.5, les opérations de création/mise à jour/suppression de sous-documents sur des champs JSONB sont prises en charge de manière native par la base de données, sans extension nécessaire. les fonctions.

Voir: JSONB modifiant les opérateurs et les fonctions

9
bguiz

J'ai écrit pour moi une petite fonction qui fonctionne de manière récursive dans Postgres 9.4. J'ai eu le même problème (bien, ils ont résolu une partie de ce mal de tête dans Postgres 9.5). Quoi qu'il en soit, 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.

5
J. Raczkiewicz

Peut-être que: UPDATE test SET data = '"mon-autre-nom"' :: json WHERE id = 1;

Cela a fonctionné avec mon cas, où les données sont de type JSON

3
Gianluigi Sartori

mettre à jour l'attribut 'name':

UPDATE test SET data=data||'{"name":"my-other-name"}' WHERE id = 1;

et si vous voulez supprimer par exemple les attributs 'name' et 'tags':

UPDATE test SET data=data-'{"name","tags"}'::text[] WHERE id = 1;
3
Arthur

Matheus de Oliveira a créé des fonctions pratiques pour les opérations JSON CRUD dans postgresql. Ils peuvent être importés avec la directive\i. Notez la fourche jsonb des fonctions si jsonb si votre type de données.

9,3 json https://Gist.github.com/matheusoliveira/9488951

9.4 jsonb https://Gist.github.com/inindev/2219dff96851928c2282

2
John Clark