Wie man mehrere Array-Elemente in mongodb aktualisiert

Ich habe ein Mongo-Dokument, das eine Reihe von Elementen enthält.

Ich möchte das Attribut .handled aller Objekte in dem Array zurücksetzen, wo .profile = XX.

Das Dokument hat die folgende Form:

 { "_id": ObjectId("4d2d8deff4e6c1d71fc29a07"), "user_id": "714638ba-2e08-2168-2b99-00002f3d43c0", "events": [{ "handled": 1, "profile": 10, "data": "....." } { "handled": 1, "profile": 10, "data": "....." } { "handled": 1, "profile": 20, "data": "....." } ... ] } 

Also habe ich folgendes versucht:

 .update({"events.profile":10},{$set:{"events.$.handled":0}},false,true) 

Es aktualisiert jedoch nur das erste übereinstimmende Array-Element in jedem Dokument. (Das ist das definierte Verhalten für $ – der positionelle Operator .)

Wie kann ich alle übereinstimmenden Array-Elemente aktualisieren?

   

In diesem Moment ist es nicht möglich, den Positionsoperator zu verwenden, um alle Elemente in einem Array zu aktualisieren. Siehe JIRA http://jira.mongodb.org/browse/SERVER-1243

Als eine Arbeit können Sie:

  • Aktualisieren Sie jedes Element einzeln (events.0.handled events.1.handled …) oder …
  • Lesen Sie das Dokument, führen Sie die Änderungen manuell aus und speichern Sie es, indem Sie das ältere ersetzen (aktivieren Sie “Update if Current”, wenn Sie atomare Updates sicherstellen möchten)

Was für mich funktionierte war das:

 db.collection.find({ _id: ObjectId('4d2d8deff4e6c1d71fc29a07') }) .forEach(function (doc) { doc.events.forEach(function (event) { if (event.profile === 10) { event.handled=0; } }); db.collection.save(doc); }); 

Ich denke, es ist klarer für Mongo-Neulinge und alle, die mit JQuery & Friends vertraut sind.

Mit der Veröffentlichung von MongoDB 3.6 (verfügbar im Entwicklungszweig von MongoDB 3.5.12) können Sie jetzt mehrere Array-Elemente in einer einzigen Anfrage aktualisieren.

Dies verwendet die gefilterte positionelle $[] Aktualisierungsoperatorsyntax, die in dieser Version eingeführt wurde:

 db.collection.update( { "events.profile":10 }, { "$set": { "events.$[elem].handled": 0 } }, { "arrayFilters": [{ "elem.profile": 10 }], "multi": true } ) 

Die an die Optionen für die .update() oder sogar .updateOne() , .updateMany() , .findOneAndUpdate() oder .bulkWrite() -Methode übergebenen "arrayFilters" die Bedingungen an, die für den Bezeichner in der update-statement übereinstimmen müssen. Alle Elemente, die der angegebenen Bedingung entsprechen, werden aktualisiert.

Es wurde darauf hingewiesen, dass das "multi" wie es im Zusammenhang mit der Frage gegeben wurde, in der Erwartung verwendet wurde, dass dies “mehrere Elemente aktualisieren” würde, aber dies war und ist nicht der Fall. Die Verwendung gilt hier für “mehrere Dokumente”, wie es in modernen API-Versionen immer der Fall war oder jetzt als obligatorische Einstellung von .updateMany() .

HINWEIS Etwas ironisch, da dies im Argument “Optionen” für .update() und ähnliche Methoden angegeben ist, ist die Syntax im Allgemeinen kompatibel mit allen aktuellen Release- .update() .

Dies gilt jedoch nicht für die mongo Shell, da die Methode dort implementiert ist (ironisch aus arrayFilters Abwärtskompatibilität). Das arrayFilters Argument wird nicht erkannt und durch eine interne Methode entfernt, die die Optionen analysiert, um “Rückwärtskompatibilität” zu liefern. mit früheren MongoDB-Serverversionen und einer “Legacy” .update() API-Aufrufsyntax.

Wenn Sie also den Befehl in der mongo Shell oder anderen “Shell-basierten” Produkten (insbesondere Robo 3T) verwenden möchten, benötigen Sie eine aktuelle Version aus dem Entwicklungszweig oder der Produktionsversion ab 3.6.

Siehe auch positional all $[] das auch “multiple array elements” aktualisiert, aber nicht auf bestimmte Bedingungen zutrifft und für alle Elemente im Array gilt, wo dies die gewünschte Aktion ist.

Siehe auch Aktualisieren eines verschachtelten Arrays mit MongoDB, um zu erfahren, wie diese neuen positionalen Operatoren auf “verschachtelte” Array-Strukturen angewendet werden, wobei “Arrays innerhalb anderer Arrays” sind.

WICHTIG – Aktualisierte Installationen von früheren Versionen “haben” möglicherweise keine MongoDB-functionen aktiviert, was ebenfalls dazu führen kann, dass statementen fehlschlagen. Sie sollten sicherstellen, dass Ihr Upgrade-Vorgang mit Details wie Index-Upgrades abgeschlossen ist und anschließend ausgeführt wird

  db.adminCommand( { setFeatureCompatibilityVersion: "3.6" } ) 

Dies ermöglichte functionen wie die neuen Positionsaktualisierungsoperatoren und andere. Sie können auch überprüfen mit:

  db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } ) 

Um die aktuelle Einstellung zurückzugeben

Dies kann auch mit einer while-Schleife erreicht werden, die überprüft, ob noch Dokumente vorhanden sind, die noch nicht aktualisierte Unterdokumente enthalten. Diese Methode bewahrt die Atomizität Ihrer Aktualisierungen (was viele der anderen Lösungen hier nicht tun).

 var query = { events: { $elemMatch: { profile: 10, handled: { $ne: 0 } } } }; while (db.yourCollection.find(query).count() > 0) { db.yourCollection.update( query, { $set: { "events.$.handled": 0 } }, { multi: true } ); } 

Die Häufigkeit, mit der die Schleife ausgeführt wird, entspricht der maximalen Anzahl von Fällen, in denen Filialdokumente mit einem profile von 10 und einem Wert ungleich 0 in einem der Dokumente in der Sammlung vorkommen. Wenn Sie also 100 Dokumente in Ihrer Sammlung haben und einer von ihnen drei Filialdokumente hat, die mit der query übereinstimmen und alle anderen Dokumente weniger übereinstimmende Filialdokumente haben, wird die Schleife dreimal ausgeführt.

Diese Methode vermeidet die Gefahr, dass andere Daten, die während der Ausführung dieses Skripts von einem anderen process aktualisiert werden könnten, verfälscht werden. Es minimiert auch die Menge der Daten, die zwischen Client und Server übertragen werden.

Dies bezieht sich in der Tat auf das seit langem bestehende Problem bei http://jira.mongodb.org/browse/SERVER-1243, wo es tatsächlich eine Reihe von Herausforderungen für eine klare Syntax gibt, die “alle Fälle” unterstützt, wo mehrere Array-Übereinstimmungen sind gefunden. Es gibt in der Tat bereits Methoden, die bei Lösungen für dieses Problem “helfen”, wie zum Beispiel Bulk-Operationen, die nach diesem ursprünglichen Post implementiert wurden.

Es ist immer noch nicht möglich, mehr als ein einziges übereinstimmendes Array-Element in einer einzigen Update-statement zu aktualisieren, so dass selbst bei einem “Multi” -Update nur ein einziges mathed-Element im Array für jedes Dokument in dieser Single aktualisiert werden kann Erklärung.

Die bestmögliche Lösung besteht derzeit darin, alle übereinstimmenden Dokumente zu finden und zu wiederholen und Bulk-Aktualisierungen zu verarbeiten, die es zumindest ermöglichen, dass viele Operationen in einer einzigen Anfrage mit einer einzigen Antwort gesendet werden. Sie können optional .aggregate() , um den im Suchresultat zurückgegebenen Array-Inhalt auf diejenigen zu reduzieren, die den Bedingungen für die Update-Auswahl entsprechen:

 db.collection.aggregate([ { "$match": { "events.handled": 1 } }, { "$project": { "events": { "$setDifference": [ { "$map": { "input": "$events", "as": "event", "in": { "$cond": [ { "$eq": [ "$$event.handled", 1 ] }, "$$el", false ] } }}, [false] ] } }} ]).forEach(function(doc) { doc.events.forEach(function(event) { bulk.find({ "_id": doc._id, "events.handled": 1 }).updateOne({ "$set": { "events.$.handled": 0 } }); count++; if ( count % 1000 == 0 ) { bulk.execute(); bulk = db.collection.initializeOrderedBulkOp(); } }); }); if ( count % 1000 != 0 ) bulk.execute(); 

Der .aggregate() Bereich wird funktionieren, wenn es einen “eindeutigen” Bezeichner für das Array gibt oder der gesamte Inhalt für jedes Element selbst ein “eindeutiges” Element bildet. Dies liegt daran, dass der Operator “set” in $setDifference verwendet wird, um alle false Werte zu filtern, die von der $map Operation zurückgegeben wurden, die zur Verarbeitung des Arrays für Übereinstimmungen verwendet wurde.

Wenn Ihr Array-Inhalt keine eindeutigen Elemente enthält, können Sie einen alternativen Ansatz mit $redact versuchen:

 db.collection.aggregate([ { "$match": { "events.handled": 1 } }, { "$redact": { "$cond": { "if": { "$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ] }, "then": "$$DESCEND", "else": "$$PRUNE" } }} ]) 

Wenn “behandelt” tatsächlich ein Feld ist, das auf anderen Dokumentenebenen vorhanden sein soll, werden Sie wahrscheinlich unerwartete Ergebnisse erhalten, aber es ist in Ordnung, wenn dieses Feld nur an einer Dokumentposition erscheint und eine Gleichheitsübereinstimmung ist.

Future Releases (Post 3.1 MongoDB) werden ab dem Schreiben eine $filter Operation haben, die einfacher ist:

 db.collection.aggregate([ { "$match": { "events.handled": 1 } }, { "$project": { "events": { "$filter": { "input": "$events", "as": "event", "cond": { "$eq": [ "$$event.handled", 1 ] } } } }} ]) 

Und alle Releases, die .aggregate() können den folgenden Ansatz mit $unwind , aber die Verwendung dieses Operators macht ihn aufgrund der Array-Erweiterung in der Pipeline zum .aggregate() Ansatz:

 db.collection.aggregate([ { "$match": { "events.handled": 1 } }, { "$unwind": "$events" }, { "$match": { "events.handled": 1 } }, { "$group": { "_id": "$_id", "events": { "$push": "$events" } }} ]) 

In allen Fällen, in denen die MongoDB-Version einen “Cursor” aus der Aggregatausgabe unterstützt, ist dies nur eine Frage der Auswahl eines Ansatzes und der Iteration der Ergebnisse mit demselben Codeblock, der zur Verarbeitung der Bulk-Aktualisierungsanweisungen angezeigt wird. Bulk-Operationen und “Cursor” aus der Gesamtausgabe werden in der gleichen Version (MongoDB 2.6) eingeführt und arbeiten daher in der Regel Hand in Hand zur Verarbeitung.

In noch früheren Versionen ist es wahrscheinlich am besten, einfach .find() zu verwenden, um den Cursor zurückzugeben und die Ausführung von statementen auf die Anzahl von Malen zu filtern, die das .update() für die .update() Iterationen .update() :

 db.collection.find({ "events.handled": 1 }).forEach(function(doc){ doc.events.filter(function(event){ return event.handled == 1 }).forEach(function(event){ db.collection.update({ "_id": doc._id },{ "$set": { "events.$.handled": 0 }}); }); }); 

Wenn Sie entschlossen sind, “Multi” -Updates durchzuführen, oder dies als letztendlich effizienter als die Verarbeitung mehrerer Updates für jedes übereinstimmende Dokument zu erachten, können Sie immer die maximale Anzahl möglicher Array-Übereinstimmungen bestimmen und einfach ein “Multi” -Update ausführen mal, bis im Grunde keine Dokumente mehr zu aktualisieren sind.

Ein gültiger Ansatz für MongoDB .aggregate() und 2.2-Versionen könnte auch .aggregate() , um diesen Wert zu finden:

 var result = db.collection.aggregate([ { "$match": { "events.handled": 1 } }, { "$unwind": "$events" }, { "$match": { "events.handled": 1 } }, { "$group": { "_id": "$_id", "count": { "$sum": 1 } }}, { "$group": { "_id": null, "count": { "$max": "$count" } }} ]); var max = result.result[0].count; while ( max-- ) { db.collection.update({ "events.handled": 1},{ "$set": { "events.$.handled": 0 }},{ "multi": true }) } 

Wie auch immer, es gibt bestimmte Dinge, die Sie nicht innerhalb des Updates tun möchten:

  1. Aktualisieren Sie das Array nicht “One Shot”: Wo, wenn Sie denken, dass es effizienter sein könnte, den gesamten Array-Inhalt im Code zu aktualisieren und dann nur $set das gesamte Array in jedem Dokument setzt. Dies scheint schneller zu verarbeiten, aber es gibt keine Garantie dafür, dass sich der Array-Inhalt seit dem Lesen und der Aktualisierung nicht geändert hat. Obwohl $set immer noch ein atomarer Operator ist, aktualisiert es das Array nur mit dem, was es für “die richtigen” Daten hält, und überschreibt daher wahrscheinlich alle Änderungen zwischen Lesen und Schreiben.

  2. Errechnen Sie keine zu aktualisierenden Indexwerte: Wo ähnlich wie bei der “One Shot” -Ansicht, berechnen Sie einfach die Position 0 und Position 2 (und so weiter) sind die zu aktualisierenden Elemente und kodieren diese mit einer eventuellen Aussage wie:

     { "$set": { "events.0.handled": 0, "events.2.handled": 0 }} 

    Auch hier besteht das Problem in der “Annahme”, dass die Indexwerte, die beim Lesen des Dokuments gefunden wurden, die gleichen Indexwerte in der Matrix zum Zeitpunkt der Aktualisierung sind. Wenn neue Elemente dem Array auf eine Weise hinzugefügt werden, die die Reihenfolge ändert, sind diese Positionen nicht mehr gültig und die falschen Elemente werden tatsächlich aktualisiert.

Bis eine sinnvolle Syntax für die Verarbeitung mehrerer übereinstimmender Array-Elemente in einer einzigen Update-statement festgelegt wurde, besteht die grundlegende Vorgehensweise darin, jedes übereinstimmende Array-Element entweder in einer individuellen statement (idealerweise in Bulk) zu aktualisieren oder im Wesentlichen die maximalen Array-Elemente auszuarbeiten Aktualisieren oder Aktualisieren fortsetzen, bis keine geänderten Ergebnisse mehr zurückgegeben werden. Auf jeden Fall sollten Sie “immer” positionelle $ updates für das übereinstimmende Array-Element verarbeiten, selbst wenn nur ein Element pro statement aktualisiert wird.

Bulk-Operationen sind in der Tat die “verallgemeinerte” Lösung zum Verarbeiten von Operationen, die als “Mehrfachoperationen” ausgeführt werden, und da es mehr Anwendungen dafür gibt, als lediglich mehrere Array-Elemente mit demselben Wert zu aktualisieren, wurde dies natürlich implementiert bereits, und es ist derzeit der beste Ansatz, um dieses Problem zu lösen.

Ich bin erstaunt, dass dies noch nicht in Mongo angesprochen wurde. Insgesamt scheint Mongo im Umgang mit Sub-Arrays nicht besonders gut zu sein. Sie können beispielsweise keine Sub-Arrays zählen.

Ich benutzte Javiers erste Lösung. Lies das Array in die Events und wiederhole dann das Set exp:

 var set = {}, i, l; for(i=0,l=events.length;i 

Dies kann in eine function unter Verwendung eines callbacks für den konditionalen Test abstrahiert werden

Ich habe Folgendes versucht und es funktioniert gut.

 .update({'events.profile': 10}, { '$set': {'events.$.handled': 0 }},{ safe: true, multi:true }, callback function); 

// Callback-function im Falle von Nodejs

Eigentlich ist der Befehl save nur für die Instanz der class Document. Das haben viele Methoden und Attribute. Sie können also die lean () -function verwenden, um die Arbeitsbelastung zu reduzieren. Siehe hier. https://hashnode.com/post/why-are-mongoose-mongodb-odm-lean-queries-faster-than-normal-queries-cillvawhq0062kj53asxoyn7j

Ein weiteres Problem mit der Speicherfunktion, bei dem gleichzeitig Konfliktdaten mit mehreren Speichern gespeichert werden. Model.Update wird Daten konsistent machen. So, um mehrere Elemente in einem Dokumentarray zu aktualisieren. Verwenden Sie Ihre vertraute Programmiersprache und versuchen Sie so etwas, ich benutze Mungo in dem:

 User.findOne({'_id': '4d2d8deff4e6c1d71fc29a07'}).lean().exec() .then(usr =>{ if(!usr) return usr.events.forEach( e => { if(e && e.profile==10 ) e.handled = 0 }) User.findOneAndUpdate( {'_id': '4d2d8deff4e6c1d71fc29a07'}, {$set: {events: usr.events}}, {new: true} ).lean().exec().then(updatedUsr => console.log(updatedUsr)) }) 

Ich habe nach einer Lösung gesucht, die den neusten Treiber für C # 3.6 verwendet, und hier ist der Fix, auf den ich mich schließlich festgelegt habe. Der Schlüssel hier ist die Verwendung von “$ []”, was laut MongoDB ab Version 3.6 neu ist. Siehe https://docs.mongodb.com/manual/reference/operator/update/positional-all/#up. S [] für weitere Informationen.

Hier ist der Code:

 { var filter = Builders.Filter.Where(i => i.ID != null); var update = Builders.Update.Unset("area.$[].discoveredBy"); var result = collection.UpdateMany(filter, update, new UpdateOptions { IsUpsert = true}); } 

Für mehr Kontext siehe meinen ursprünglichen Beitrag hier: Entfernen Array-Element aus allen Dokumenten mit MongoDB C # -Treiber

Ich wollte nur eine andere Lösung hinzufügen, die für mich funktionierte und ziemlich einfach ist. Hier ist es nur ein Array von Tags (Strings), um ein Tag namens “test” auf “changed” zu setzen.

 myDocuments.find({tags: "test" }, {fields: {_id: 1}}).forEach(function (doc) { myDocuments.update( {_id: doc._id, tags: "test"}, {$set:{'tags.$': "changed"}}); });