web-dev-qa-db-fra.com

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

Compte tenu de ce document enregistré dans MongoDB 

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

Un objet contenant de nouvelles informations sur param2 et param3 provenant du monde extérieur doit être enregistré.

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

Je souhaite fusionner/superposer les nouveaux champs sur l'état existant de l'objet afin que param1 ne soit pas supprimé

Ce faisant

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

Cela mènera à MongoDB fait exactement comme il a été demandé, et définit some_key à cette valeur. remplacer l'ancien.

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

Comment faire pour que MongoDB ne mette à jour que les nouveaux champs (sans les préciser un par un)? 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é

134
Eran Medan

Si je comprends bien la question, vous souhaitez 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 déjà définis (même si leur valeur est différente).

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 correspondant pour vous assurer que vous n'obtiendrez pas de mises à jour simultanées).


Une autre lecture de votre question serait que vous êtes satisfait de $set, mais que vous ne voulez pas définir explicitement tous les champs. Comment voulez-vous transmettre les données alors?

Vous savez que vous pouvez faire ce qui suit:

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

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

Exemple:

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

Si vous voulez mettre à jour uniquement param2, il est faux de le faire:

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

Tu dois utiliser:

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

J'ai donc écrit une fonction quelque chose comme ça:

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'));
88
mehmatrix

Vous pouvez utiliser la notation par points pour accéder aux champs et les définir au plus profond des 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 uniquement some_key.param2 et 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. Cela est également utile pour ajouter de nouvelles propriétés à un objet sans affecter les propriétés existantes.

14
Samir Talwar

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

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

Il a la fonction .flatten qui vous permet de changer un objet en un ensemble plat de propriétés qui pourrait ensuite être attribué au modificateur $ set, sans vous soucier du fait que toute propriété de votre objet DB existant sera supprimée/remplacée 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 puis cela forme un sélecteur parfait - il mettra à jour UNIQUEMENT les propriétés données ... __ EDIT: J'aime être archéologue quelques fois;)

10
SzybkiSasza

Mongo vous permet de mettre à jour des documents imbriqués en utilisant une convention .. Jetez un oeil: Mise à jour des documents imbriqués dans mongodb . Voici une autre question du passé concernant une mise à jour de fusion, comme celle que vous recherchez, je crois: Mise à jour atomique de MongoDB via le document 'fusion'

7
Pavel Veller

J'ai eu du succès en le faisant de cette façon:

db.collection.update(  { _id:...} , { $set: { 'key.another_key' : new_info  } } );

J'ai une fonction qui gère mon profile met à jour dynamiquement

function update(prop, newVal) {
  const str = `profile.${prop}`;
  db.collection.update( { _id:...}, { $set: { [str]: newVal } } );
}

Remarque: "profil" est spécifique à mon implémentation, il s'agit simplement de la chaîne de la clé que vous souhaitez modifier.

3
Matt Smith

Il semble que vous puissiez définir isPartialObject qui pourrait accomplir ce que vous voulez.

1
Patrick Tescher

Ouais, le meilleur moyen est de convertir la notation de l'objet en une représentation de chaîne clé-valeur plate, comme mentionné dans ce commentaire: https://stackoverflow.com/a/39357531/2529199

Je souhaitais mettre en évidence une méthode alternative utilisant cette bibliothèque NPM: https://www.npmjs.com/package/dot-object qui vous permet de manipuler différents objets à l'aide de la notation par points.

J'ai utilisé ce modèle pour créer par programme une propriété d'objet imbriqué lors de l'acceptation de la valeur-clé en tant que variable de fonction, comme suit:

const dot = require('dot-object');

function(docid, varname, varvalue){
  let doc = dot.dot({
      [varname]: varvalue 
  });

  Mongo.update({_id:docid},{$set:doc});
}

Ce modèle me permet d’utiliser indifféremment les propriétés imbriquées et les propriétés à un seul niveau et de les insérer proprement dans Mongo.

Si vous avez besoin de manipuler des objets JS au-delà de Mongo, en particulier côté client, mais de manière cohérente lorsque vous travaillez avec Mongo, cette bibliothèque vous offre davantage d'options que le module mongo-dot-notation NPM mentionné précédemment.

P.S Au départ, je voulais simplement mentionner cela comme un commentaire, mais apparemment, mon représentant S/O n’est pas assez haut pour poster un commentaire. Donc, n'essayant pas de faire écho au commentaire de SzybkiSasza, je voulais simplement mettre en évidence la fourniture d'un module alternatif.

1
Ashwin Shankar

À partir de _Mongo 4.2_, db.collection.update() peut accepter un pipeline d'agrégation permettant d'utiliser des opérateurs d'agrégation tels que $addFields , qui génère tous les champs existants à partir des documents d'entrée. et champs nouvellement ajoutés:

_var new_info = { param2: "val2_new", param3: "val3_new" }

// { some_key: { param1: "val1", param2: "val2", param3: "val3" } }
// { some_key: { param1: "val1", param2: "val2"                 } }
db.collection.update({}, [{ $addFields: { some_key: new_info } }], { multi: true })
// { some_key: { param1: "val1", param2: "val2_new", param3: "val3_new" } }
// { some_key: { param1: "val1", param2: "val2_new", param3: "val3_new" } }
_
  • La première partie _{}_ est la requête de correspondance, filtrant les documents à mettre à jour (dans ce cas, tous les documents).

  • La deuxième partie _[{ $addFields: { some_key: new_info } }]_ est le pipeline d'agrégation de mises à jour:

    • Notez les crochets qui indiquent l’utilisation d’un pipeline d’agrégation.
    • Puisqu'il s'agit d'un pipeline d'agrégation, nous pouvons utiliser $addFields .
    • _$addFields_ effectue exactement ce dont vous avez besoin: mettre à jour l'objet afin que le nouvel objet se superpose/se fusionne avec l'objet existant:
    • Dans ce cas, _{ param2: "val2_new", param3: "val3_new" }_ sera fusionné dans le _some_key_ existant en conservant _param1_ intact et en ajoutant ou en remplaçant les deux _param2_ et _param3_.
  • N'oubliez pas _{ multi: true }_, sinon seul le premier document correspondant sera mis à jour.

0
Xavier Guihot
    // where clause DBObject
    DBObject query = new BasicDBObject("_id", new ObjectId(id));

    // modifications to be applied
    DBObject update = new BasicDBObject();

    // set new values
    update.put("$set", new BasicDBObject("param2","value2"));

   // update the document
    collection.update(query, update, true, false); //3rd param->upsertFlag, 4th param->updateMultiFlag

Si vous avez plusieurs champs à mettre à jour

        Document doc = new Document();
        doc.put("param2","value2");
        doc.put("param3","value3");
        update.put("$set", doc);
0
Vino

Créez un objet de mise à jour avec les noms de propriété, y compris le chemin de points nécessaire. ("somekey." + de l'exemple d'OP), puis utilisez-le pour faire la mise à jour.

//the modification that's requested
updateReq = { 
   param2 : "val2_new",
   param3 : "val3_new"   
}

//build a renamed version of the update request
var update = {};
for(var field in updateReq){
    update["somekey."+field] = updateReq[field];
}

//apply the update without modifying fields not originally in the update
db.collection.update({._id:...},{$set:update},{upsert:true},function(err,result){...});
0
Andrew

J'ai essayé findAndModify() pour mettre à jour un champ particulier dans un objet préexistant.

https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/

0
nick-s

utiliser $ set faire ce processus

.update({"_id": args.dashboardId, "viewData._id": widgetId}, {$set: {"viewData.$.widgetData": widgetDoc.widgetData}})
.exec()
.then(dashboardDoc => {
    return {
        result: dashboardDoc
    };
}); 
0
KARTHIKEYAN.A