245 votes

Comment mettre partiellement à jour un objet dans MongoDB afin que le nouvel objet se superpose/fusionne avec l'objet existant ?

Étant donné ce document enregistré dans MongoDB

{
   _id : ...,
   some_key: { 
        param1 : "val1",
        param2 : "val2",
        param3 : "val3"
   }
}

Un objet contenant de nouvelles informations sur param2 y param3 du monde extérieur doit être sauvé

var new_info = {
    param2 : "val2_new",
    param3 : "val3_new"
};

Je veux fusionner / superposer les nouveaux champs sur l'état existant de l'objet afin que le paramètre 1 ne soit pas supprimé.

Faire cela

db.collection.update(  { _id:...} , { $set: { some_key : new_info  } } 

MongoDB fait exactement ce qu'on lui a demandé, et définit some_key à cette valeur. en remplaçant l'ancienne.

{
   _id : ...,
   some_key: { 
      param2 : "val2_new",
      param3 : "val3_new"
   }
}

Quel est le moyen de faire en sorte que MongoDB mette à jour uniquement les nouveaux champs (sans les déclarer un par un explicitement) ? pour obtenir ceci :

{
   _id : ...,
   some_key: { 
        param1 : "val1",
        param2 : "val2_new",
        param3 : "val3_new"
   }
}

J'utilise le client Java, mais tout exemple sera apprécié.

133voto

mehmatrix Points 156

Je l'ai résolu avec ma propre fonction. Si vous voulez mettre à jour un champ spécifique dans un document, vous devez l'adresser clairement.

Exemple :

{
    _id : ...,
    some_key: { 
        param1 : "val1",
        param2 : "val2",
        param3 : "val3"
    }
}

Si vous voulez mettre à jour uniquement le paramètre 2, c'est une erreur :

db.collection.update(  { _id:...} , { $set: { some_key : new_info  } }  //WRONG

Vous devez utiliser :

db.collection.update(  { _id:...} , { $set: { some_key.param2 : new_info  } } 

J'ai donc écrit une fonction comme celle-là :

function _update($id, $data, $options=array()){

    $temp = array();
    foreach($data as $key => $value)
    {
        $temp["some_key.".$key] = $value;
    } 

    $collection->update(
        array('_id' => $id),
        array('$set' => $temp)
    );

}

_update('1', array('param2' => 'some data'));

120voto

Thilo Points 108673

Si je comprends bien la question, vous voulez mettre à jour un document avec le contenu d'un autre document, mais uniquement les champs qui ne sont pas déjà présents, et ignorer complètement les champs qui sont déjà définis (même s'ils ont une autre valeur).

Il n'y a aucun moyen de faire cela en une seule commande.

Vous devez d'abord interroger le document, déterminer ce que vous voulez $set puis le mettre à jour (en utilisant les anciennes valeurs comme filtre de correspondance pour s'assurer que vous ne recevez pas de mises à jour simultanées entre les deux).


Une autre lecture de votre question serait que vous êtes heureux avec $set mais vous ne souhaitez pas définir explicitement tous les champs. Comment transmettre les données dans ce cas ?

Vous savez que vous pouvez faire ce qui suit :

db.collection.update(  { _id:...} , { $set: someObjectWithNewData }

27voto

Samir Talwar Points 9307

Vous pouvez utiliser la notation par points pour accéder à des champs et les définir à l'intérieur d'objets, sans affecter les autres propriétés de ces objets.

Étant donné l'objet que vous avez spécifié ci-dessus :

> db.test.insert({"id": "test_object", "some_key": {"param1": "val1", "param2": "val2", "param3": "val3"}})
WriteResult({ "nInserted" : 1 })

Nous pouvons mettre à jour juste some_key.param2 y some_key.param3 :

> db.test.findAndModify({
... query: {"id": "test_object"},
... update: {"$set": {"some_key.param2": "val2_new", "some_key.param3": "val3_new"}},
... new: true
... })
{
    "_id" : ObjectId("56476e04e5f19d86ece5b81d"),
    "id" : "test_object",
    "some_key" : {
        "param1" : "val1",
        "param2" : "val2_new",
        "param3" : "val3_new"
    }
}

Vous pouvez plonger aussi profondément que vous le souhaitez. C'est également utile pour ajouter de nouvelles propriétés à un objet sans affecter les propriétés existantes.

24voto

SzybkiSasza Points 1096

La meilleure solution est d'extraire les propriétés de l'objet et d'en faire des paires clé-valeur plates à notation par points. Vous pouvez utiliser par exemple cette bibliothèque :

https://www.npmjs.com/package/mongo-dot-notation

Il a .flatten qui vous permet de transformer un objet en un ensemble plat de propriétés qui peuvent ensuite être données au modificateur $set, sans craindre que toutes les propriétés de votre objet DB existant soient supprimées/écrites sans nécessité.

Tiré de mongo-dot-notation docs :

var person = {
  firstName: 'John',
  lastName: 'Doe',
  address: {
    city: 'NY',
    street: 'Eighth Avenu',
    number: 123
  }
};

var instructions = dot.flatten(person)
console.log(instructions);
/* 
{
  $set: {
    'firstName': 'John',
    'lastName': 'Doe',
    'address.city': 'NY',
    'address.street': 'Eighth Avenu',
    'address.number': 123
  }
}
*/

Et ensuite, il forme un sélecteur parfait - il mettra à jour UNIQUEMENT les propriétés données. EDIT : J'aime être archéologue parfois ;)

12voto

Pavel Veller Points 3377

Mongo vous permet de mettre à jour des documents imbriqués à l'aide d'une fonction . convention. Jetez un coup d'oeil : Mise à jour des documents imbriqués dans mongodb . Voici une autre question du passé concernant une mise à jour de la fusion, comme celle que vous recherchez, je crois : Mise à jour atomique de MongoDB via un document "merge" (fusion)

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