Wie aktualisiere ich ein Objekt in MongoDB teilweise, damit das neue Objekt mit dem vorhandenen Objekt überlagert / zusammengeführt wird?

180

Angesichts dieses in MongoDB gespeicherten Dokuments

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

Ein Objekt mit neuen Informationen zur param2und param3von der Außenwelt muss gespeichert werden

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

Ich möchte die neuen Felder über den vorhandenen Status des Objekts zusammenführen / überlagern, damit param1 nicht entfernt wird

Dies tun

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

Wird dazu führen, dass MongoDB genau das tut, was es verlangt hat, und some_key auf diesen Wert setzt. Ersetzen des alten.

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

Wie kann MongoDB nur neue Felder aktualisieren (ohne sie explizit einzeln anzugeben)? um dies zu bekommen:

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

Ich verwende den Java-Client, aber jedes Beispiel wird geschätzt

Eran Medan
quelle
2
Bitte stimmen Sie für das $ Merge-Problem ab: jira.mongodb.org/browse/SERVER-21094
Ali

Antworten:

105

Wenn ich die Frage richtig verstehe, möchten Sie ein Dokument mit dem Inhalt eines anderen Dokuments aktualisieren, jedoch nur mit den Feldern, die noch nicht vorhanden sind, und die bereits festgelegten Felder vollständig ignorieren (auch wenn sie einen anderen Wert haben).

Es gibt keine Möglichkeit, dies in einem einzigen Befehl zu tun.

Sie müssen das Dokument zuerst abfragen, herausfinden, was Sie möchten, $setund es dann aktualisieren (indem Sie die alten Werte als passenden Filter verwenden, um sicherzustellen, dass Sie nicht gleichzeitig Aktualisierungen erhalten).


Eine andere Lesart Ihrer Frage wäre, dass Sie zufrieden sind $set, aber nicht alle Felder explizit festlegen möchten. Wie würden Sie dann die Daten weitergeben?

Sie wissen, dass Sie Folgendes tun können:

db.collection.update(  { _id:...} , { $set: someObjectWithNewData } 
Thilo
quelle
Wenn Sie alle Felder unbedingt festlegen möchten, können Sie den Parameter $ multi wie db.collection.update( { _id:...} , { $set: someObjectWithNewData, { $multi: true } } )
folgt verwenden
12
Es gibt keinen $ multi-Parameter. Es gibt eine Multi-Option (mit der Sie verknüpft sind), die jedoch keinen Einfluss darauf hat, wie Felder aktualisiert werden. Es steuert, ob Sie ein einzelnes Dokument oder alle Dokumente aktualisieren, die dem Abfrageparameter entsprechen.
Bob Kerns
1
Das macht überhaupt keinen Sinn. Wenn es um Mongo geht, wird es auch bevorzugt, dass Aktualisierungen atomare Operationen haben. Das erste Lesen führt zu einem fehlerhaften Lesen, wenn jemand anderes Ihre Daten ändert. Und da Mongo keine Transaktionen hat, werden die Daten für Sie gerne beschädigt.
Froginvasion
@froginvasion: Sie können es atomar machen, indem Sie die alten Werte als passenden Filter für das Update verwenden. Auf diese Weise schlägt Ihr Update fehl, wenn in der Zwischenzeit ein widersprüchliches gleichzeitiges Update aufgetreten ist. Sie können dies dann erkennen und entsprechend handeln. Dies nennt man optimistisches Sperren. Aber ja, es hängt davon ab, ob Ihre Anwendung sie ordnungsgemäß implementiert. Mongo selbst verfügt nicht über integrierte Transaktionen.
Thilo
Ich denke, dies ist eine allgemeinere Frage des Zusammenführens zweier Objekte in Javascript. IIRC die Idee ist, einfach die Eigenschaften des neuen dem alten zuzuweisen
information_interchange
109

Ich habe es mit meiner eigenen Funktion gelöst. Wenn Sie das angegebene Feld im Dokument aktualisieren möchten, müssen Sie es klar ansprechen.

Beispiel:

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

Wenn Sie nur param2 aktualisieren möchten, ist dies falsch:

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

Sie müssen verwenden:

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

Also habe ich so eine Funktion geschrieben:

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'));
Mehmatrix
quelle
11
Dies sollte die akzeptierte Antwort sein. Für eine bessere flattenObject-Funktion siehe gist.github.com/penguinboy/762197
steph643
Vielen Dank, Ihre Antwort hat mir geholfen, mich zu führen. Ich konnte den Wert eines bestimmten Schlüssels eines Benutzers in der Benutzersammlung wie folgt ändern:db.users.update( { _id: ObjectId("594dbc3186bb5c84442949b1") }, { $set: { resume: { url: "http://i.imgur.com/UFX0yXs.jpg" } } } )
Luke Schoen
Ist diese Operation atomar und isoliert? Das heißt, wenn 2 parallele Threads versuchen, 2 verschiedene Felder desselben Dokuments festzulegen, führt dies zu einer Rennbedingung und einem unvorhersehbaren Ergebnis
so zufälliger Typ
21

Sie können die Punktnotation verwenden, um auf Felder tief in Objekten zuzugreifen und diese festzulegen, ohne die anderen Eigenschaften dieser Objekte zu beeinflussen.

Angesichts des oben angegebenen Objekts:

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

Wir können nur aktualisieren some_key.param2und 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"
    }
}

Sie können so tief eintauchen, wie Sie möchten. Dies ist auch nützlich, um einem Objekt neue Eigenschaften hinzuzufügen, ohne die vorhandenen zu beeinflussen.

Samir Talwar
quelle
Kann ich einen neuen Schlüssel hinzufügen ... wie "irgendein Schlüssel": {"param1": "val1", "param2": "val2_new", "param3": "val3_new", "param4": "val4_new",}
Urvashi Soni
16

Die beste Lösung besteht darin, Eigenschaften aus dem Objekt zu extrahieren und sie zu flachen Schlüssel-Wert-Paaren mit Punktnotation zu machen. Sie könnten zum Beispiel diese Bibliothek verwenden:

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

Es hat .flatten Funktion, mit der Sie ein Objekt in einen flachen Satz von Eigenschaften ändern können, die dann dem Modifikator $ set zugewiesen werden können, ohne befürchten zu müssen, dass eine Eigenschaft Ihres vorhandenen DB-Objekts ohne Notwendigkeit gelöscht / überschrieben wird.

Entnommen aus mongo-dot-notationDokumenten:

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
  }
}
*/

Und dann bildet es den perfekten Selektor - es aktualisiert NUR die angegebenen Eigenschaften. EDIT: Ich bin manchmal gerne Archäologe;)

SzybkiSasza
quelle
6

Ich hatte Erfolg damit:

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

Ich habe eine Funktion , die meine Griffe Profil dynamisch Updates

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

Hinweis: 'Profil' ist spezifisch für meine Implementierung. Es ist nur die Zeichenfolge des Schlüssels, den Sie ändern möchten.

Matt Smith
quelle
1

Es sieht so aus, als könnten Sie isPartialObject festlegen, um das zu erreichen, was Sie wollen.

Patrick Tescher
quelle
Ich habe es versucht. Sie können keine Teilobjekte speichern, wenn ich mich nicht irre ... aber danke
Eran Medan
1
    // 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

Wenn Sie mehrere Felder aktualisieren möchten

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

Starten Mongo 4.2, db.collection.update()kann eine Aggregation Pipeline akzeptieren, der Betreiber Aggregation ermöglicht wie $addFields, das gibt alle vorhandenen Felder aus den Eingangsdokumenten und neu hinzugefügte Feldern:

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" } }
  • Der erste Teil {}ist die Übereinstimmungsabfrage, bei der gefiltert wird, welche Dokumente aktualisiert werden sollen (in diesem Fall alle Dokumente).

  • Der zweite Teil [{ $addFields: { some_key: new_info } }]ist die Update-Aggregations-Pipeline:

    • Beachten Sie die eckigen Klammern, die die Verwendung einer Aggregationspipeline angeben.
    • Da dies eine Aggregationspipeline ist, können wir verwenden $addFields.
    • $addFields führt genau das aus, was Sie benötigen: Aktualisieren des Objekts, sodass das neue Objekt mit dem vorhandenen Objekt überlagert / zusammengeführt wird:
    • In diesem Fall { param2: "val2_new", param3: "val3_new" }wird in das vorhandene zusammengeführt, some_keyindem Sie param1unberührt bleiben und entweder beide param2und entweder hinzufügen oder ersetzen param3.
  • Vergessen Sie nicht { multi: true }, sonst wird nur das erste übereinstimmende Dokument aktualisiert.

Xavier Guihot
quelle
1
db.collection.update(  { _id:...} , { $set: { some_key : new_info  } } 

zu

db.collection.update( { _id: ..} , { $set: { some_key: { param1: newValue} } } ); 

Ich hoffe das hilft!

nguyenthang338
quelle
1

Sie könnten lieber einen Upsert durchführen. Dieser Vorgang in MongoDB wird verwendet, um Dokumente in der Sammlung zu speichern. Wenn das Dokument den Abfragekriterien entspricht, wird ein Aktualisierungsvorgang ausgeführt, andernfalls wird ein neues Dokument in die Sammlung eingefügt.

etwas ähnliches wie unten

db.employees.update(
    {type:"FT"},
    {$set:{salary:200000}},
    {upsert : true}
 )
Pravin
quelle
0

Verwenden Sie $ set, um diesen Vorgang auszuführen

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

Erstellen Sie ein Aktualisierungsobjekt mit den Eigenschaftsnamen einschließlich des erforderlichen Punktpfads. ("somekey." + aus dem Beispiel von OP), und verwenden Sie dann das Update.

//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){...});
Andrew
quelle
0

Ja, der beste Weg ist, die Objektnotation in eine flache Darstellung von Schlüsselwertzeichenfolgen zu konvertieren, wie in diesem Kommentar erwähnt: https://stackoverflow.com/a/39357531/2529199

Ich wollte eine alternative Methode mit dieser NPM-Bibliothek hervorheben: https://www.npmjs.com/package/dot-object, mit der Sie verschiedene Objekte mithilfe der Punktnotation bearbeiten können.

Ich habe dieses Muster verwendet, um programmgesteuert eine verschachtelte Objekteigenschaft zu erstellen, wenn der Schlüsselwert wie folgt als Funktionsvariable akzeptiert wurde:

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

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

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

Mit diesem Muster kann ich verschachtelte und einstufige Eigenschaften austauschbar verwenden und sie sauber in Mongo einfügen.

Wenn Sie mit JS-Objekten außerhalb von Mongo herumspielen müssen, insbesondere auf der Clientseite, aber bei der Arbeit mit Mongo konsistent sind, bietet Ihnen diese Bibliothek mehr Optionen als das zuvor erwähnte mongo-dot-notationNPM-Modul.

PS Ich wollte dies ursprünglich nur als Kommentar erwähnen, aber anscheinend ist mein S / O-Mitarbeiter nicht hoch genug, um einen Kommentar zu schreiben. Um nicht auf SzybkiSaszas Kommentar einzugehen, wollte ich nur die Bereitstellung eines alternativen Moduls hervorheben.

Ashwin Shankar
quelle
0

Sie sollten darüber nachdenken, das Objekt austauschbar zu aktualisieren, und dann einfach das Objekt mit den aktualisierten Feldern speichern. So etwas wie unten gemacht

function update(_id) {
    return new Promise((resolve, reject) => {

        ObjModel.findOne({_id}).exec((err, obj) => {

            if(err) return reject(err)

            obj = updateObject(obj, { 
                some_key: {
                    param2 : "val2_new",
                    param3 : "val3_new"
                }
            })

            obj.save((err, obj) => {
                if(err) return reject(err)
                resolve(obj)
            })
        })
    })
}


function updateObject(obj, data) {

    let keys = Object.keys(data)

    keys.forEach(key => {

        if(!obj[key]) obj[key] = data[key]

        if(typeof data[key] == 'object') 
            obj[key] = updateObject(obj[key], data[key])
        else
            obj[key] = data[key]
    })

    return obj
}
Ivan Bravo Mendoza
quelle
0

Sie müssen eingebettete Dokumente verwenden (Zeichenfolge für das Pfadobjekt).

let update = {}
Object.getOwnPropertyNames(new_info).forEach(param => {
   update['some_key.' + param] = new_info[param]
})

In JavaScript können Sie den Spread Operator (...) zum Aktualisieren verwenden

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