Dies bezieht sich in der Tat auf das langjährige Problem unter http://jira.mongodb.org/browse/SERVER-1243, bei dem es tatsächlich eine Reihe von Herausforderungen für eine klare Syntax gibt, die "alle Fälle" unterstützt, in denen mehrere Array-Übereinstimmungen vorliegen gefunden. Es gibt bereits Methoden, die bei der Lösung dieses Problems "helfen", wie z. B. Massenoperationen, die nach diesem ursprünglichen Beitrag implementiert wurden.
Es ist immer noch nicht möglich, mehr als ein einzelnes übereinstimmendes Array-Element in einer einzelnen Update-Anweisung zu aktualisieren. Selbst mit einem "Multi" -Update können Sie also nur ein mathematisches Element im Array für jedes Dokument in diesem einzelnen aktualisieren Aussage.
Die derzeit bestmögliche Lösung besteht darin, alle übereinstimmenden Dokumente zu finden und zu schleifen und Massenaktualisierungen zu verarbeiten, sodass zumindest viele Vorgänge in einer einzigen Anforderung mit einer einzelnen Antwort gesendet werden können. Optional können Sie .aggregate()
den im Suchergebnis zurückgegebenen Array-Inhalt auf diejenigen 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()
Teil dort funktioniert, wenn es eine "eindeutige" Kennung für das Array gibt oder der gesamte Inhalt für jedes Element selbst ein "eindeutiges" Element bildet. Dies ist auf den Operator "set" zurückzuführen, mit dem $setDifference
alle false
Werte gefiltert werden , die von der $map
Operation zurückgegeben wurden, mit der das Array auf Übereinstimmungen verarbeitet wurde.
Wenn Ihr Array-Inhalt keine eindeutigen Elemente enthält, können Sie einen alternativen Ansatz ausprobieren mit $redact
:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
Die Einschränkung besteht darin, dass, wenn "behandelt" tatsächlich ein Feld war, das auf anderen Dokumentebenen vorhanden sein soll, Sie wahrscheinlich nicht erwartete Ergebnisse erhalten. Dies ist jedoch in Ordnung, wenn dieses Feld nur an einer Dokumentposition angezeigt wird und eine Gleichheitsübereinstimmung darstellt.
Zukünftige Versionen (nach 3.1 MongoDB) werden zum Zeitpunkt des Schreibens eine $filter
einfachere Operation haben :
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$project": {
"events": {
"$filter": {
"input": "$events",
"as": "event",
"cond": { "$eq": [ "$$event.handled", 1 ] }
}
}
}}
])
Alle Releases, die dies unterstützen, .aggregate()
können den folgenden Ansatz verwenden $unwind
, aber die Verwendung dieses Operators macht ihn aufgrund der Array-Erweiterung in der Pipeline zum am wenigsten effizienten 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 Gesamtausgabe unterstützt, müssen Sie lediglich einen Ansatz auswählen und die Ergebnisse mit demselben Codeblock iterieren, der zur Verarbeitung der Bulk-Update-Anweisungen angezeigt wird. Massenoperationen und "Cursor" aus der Gesamtausgabe werden in derselben Version (MongoDB 2.6) eingeführt und arbeiten daher normalerweise Hand in Hand für die Verarbeitung.
In noch früheren Versionen ist es wahrscheinlich am besten, nur .find()
den Cursor zurückzugeben und die Ausführung von Anweisungen so herauszufiltern, wie oft das Array-Element für die .update()
Iterationen übereinstimmt :
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 absolut entschlossen sind, "Multi" -Updates durchzuführen, oder dies als letztendlich effizienter erachten als die Verarbeitung mehrerer Updates für jedes übereinstimmende Dokument, können Sie immer die maximale Anzahl möglicher Array-Übereinstimmungen bestimmen und einfach ein "Multi" -Update ausführen, das so viele enthält Mal, bis im Grunde keine Dokumente mehr zu aktualisieren sind.
Ein gültiger Ansatz für MongoDB 2.4- und 2.2-Versionen könnte ebenfalls verwendet werden .aggregate()
, um diesen Wert zu ermitteln:
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 })
}
In jedem Fall gibt es bestimmte Dinge, die Sie im Rahmen des Updates nicht tun möchten:
Aktualisieren Sie das Array nicht auf einmal: Wenn Sie der Meinung sind, dass es möglicherweise effizienter ist, den gesamten Array-Inhalt im Code und dann nur $set
das gesamte Array in jedem Dokument zu aktualisieren . Dies scheint schneller zu verarbeiten zu sein, es gibt jedoch keine Garantie dafür, dass sich der Array-Inhalt seit dem Lesen und der Aktualisierung nicht geändert hat. Obwohl $set
es sich immer noch um einen atomaren Operator handelt, wird das Array nur mit den "Daten" aktualisiert, die es für "richtig" hält, und es ist daher wahrscheinlich, dass Änderungen zwischen Lesen und Schreiben überschrieben werden.
Berechnen Sie keine zu aktualisierenden Indexwerte: Wenn Sie dem "One-Shot" -Ansatz ähnlich sind, berechnen Sie einfach, dass Position 0
und Position 2
(usw.) die Elemente sind, um diese zu aktualisieren und zu codieren, und eventuelle Anweisungen wie:
{ "$set": {
"events.0.handled": 0,
"events.2.handled": 0
}}
Auch hier besteht das Problem in der "Annahme", dass die beim Lesen des Dokuments gefundenen Indexwerte zum Zeitpunkt der Aktualisierung dieselben Indexwerte im Array sind. Wenn dem Array neue Elemente hinzugefügt werden, die die Reihenfolge ändern, sind diese Positionen nicht mehr gültig und die falschen Elemente werden tatsächlich aktualisiert.
Bis eine vernünftige Syntax für die Verarbeitung mehrerer übereinstimmender Array-Elemente in einer einzigen Aktualisierungsanweisung festgelegt ist, besteht der grundlegende Ansatz darin, entweder jedes übereinstimmende Array-Element in einer einzelnen Anweisung (idealerweise in Bulk) zu aktualisieren oder im Wesentlichen die maximalen Array-Elemente zu ermitteln um zu aktualisieren oder weiter zu aktualisieren, bis keine geänderten Ergebnisse mehr zurückgegeben werden. In jedem Fall sollten Sie "immer" Positionsaktualisierungen$
für das übereinstimmende Array-Element verarbeiten, auch wenn dadurch nur ein Element pro Anweisung aktualisiert wird.
Massenoperationen sind in der Tat die "verallgemeinerte" Lösung für die Verarbeitung von Operationen, die sich als "Mehrfachoperationen" herausstellen. Da es dafür mehr Anwendungen gibt als nur die Aktualisierung mehrerer Array-Elemente mit demselben Wert, wurde sie natürlich implementiert bereits, und es ist derzeit der beste Ansatz, um dieses Problem zu lösen.