Cela est en fait lié au problème de longue date sur http://jira.mongodb.org/browse/SERVER-1243 où il y a en fait un certain nombre de défis à une syntaxe claire qui prend en charge "tous les cas" où les correspondances de plusieurs tableaux sont a trouvé. Il existe en fait des méthodes déjà en place qui «aident» à trouver des solutions à ce problème, telles que les opérations en masse qui ont été mises en œuvre après cet article original.
Il n'est toujours pas possible de mettre à jour plus d'un élément de tableau correspondant dans une seule instruction de mise à jour, donc même avec une mise à jour "multi", tout ce que vous pourrez mettre à jour est juste un élément mathématique dans le tableau pour chaque document de ce seul déclaration.
La meilleure solution possible à l'heure actuelle est de rechercher et de boucler tous les documents correspondants et de traiter les mises à jour groupées qui permettront au moins d'envoyer de nombreuses opérations en une seule demande avec une réponse unique. Vous pouvez éventuellement utiliser .aggregate()
pour réduire le contenu du tableau renvoyé dans le résultat de la recherche à ceux qui correspondent aux conditions de la sélection de mise à jour:
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();
La .aggregate()
partie là fonctionnera quand il y a un identifiant "unique" pour le tableau ou tout le contenu pour chaque élément forme un élément "unique" lui-même. Cela est dû à l'opérateur "set" $setDifference
utilisé pour filtrer toutes les false
valeurs renvoyées par l' $map
opération utilisée pour traiter le tableau pour les correspondances.
Si le contenu de votre tableau ne contient pas d'éléments uniques, vous pouvez essayer une autre approche avec $redact
:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
Là où sa limitation est que si "manipulé" était en fait un champ censé être présent à d'autres niveaux de document, vous obtiendrez probablement des résultats non attendus, mais c'est bien là où ce champ n'apparaît qu'à une seule position du document et correspond à l'égalité.
Les futures versions (post 3.1 MongoDB) au moment de l'écriture auront une $filter
opération qui est plus simple:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$project": {
"events": {
"$filter": {
"input": "$events",
"as": "event",
"cond": { "$eq": [ "$$event.handled", 1 ] }
}
}
}}
])
Et toutes les versions qui prennent en charge .aggregate()
peuvent utiliser l'approche suivante avec $unwind
, mais l'utilisation de cet opérateur en fait l'approche la moins efficace en raison de l'expansion de la baie dans le pipeline:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$unwind": "$events" },
{ "$match": { "events.handled": 1 } },
{ "$group": {
"_id": "$_id",
"events": { "$push": "$events" }
}}
])
Dans tous les cas où la version MongoDB prend en charge un "curseur" à partir de la sortie agrégée, il s'agit simplement de choisir une approche et d'itérer les résultats avec le même bloc de code affiché pour traiter les instructions de mise à jour en bloc. Les opérations en masse et les "curseurs" de la sortie agrégée sont introduits dans la même version (MongoDB 2.6) et travaillent donc généralement main dans la main pour le traitement.
Dans les versions encore plus anciennes, il est probablement préférable d'utiliser simplement .find()
pour renvoyer le curseur et de filtrer l'exécution des instructions uniquement au nombre de fois où l'élément du tableau est mis en correspondance pour les .update()
itérations:
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 }});
});
});
Si vous êtes absolument déterminé à faire des mises à jour «multi» ou que vous estimez que c'est finalement plus efficace que de traiter plusieurs mises à jour pour chaque document correspondant, vous pouvez toujours déterminer le nombre maximum de correspondances de tableaux possibles et simplement exécuter une mise à jour «multi» autant fois, jusqu'à ce qu'il n'y ait plus de documents à mettre à jour.
Une approche valide pour les versions MongoDB 2.4 et 2.2 pourrait également être utilisée .aggregate()
pour trouver cette valeur:
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 })
}
Quel que soit le cas, il y a certaines choses que vous ne pas voulez faire dans la mise à jour:
Ne mettez pas à jour le tableau "en une seule fois": Où, si vous pensez qu'il serait plus efficace de mettre à jour tout le contenu du tableau dans le code, puis uniquement $set
le tableau entier dans chaque document. Cela peut sembler plus rapide à traiter, mais il n'y a aucune garantie que le contenu du tableau n'a pas changé depuis sa lecture et la mise à jour effectuée. Bien qu'il $set
soit toujours un opérateur atomique, il ne mettra à jour le tableau qu'avec ce qu'il «pense» être les données correctes, et il est donc susceptible d'écraser tout changement intervenant entre la lecture et l'écriture.
Ne calculez pas les valeurs d'index à mettre à jour: là où cela est similaire à l'approche "one shot", vous calculez simplement cette position 0
et cette position 2
(et ainsi de suite) sont les éléments pour mettre à jour et coder ces éléments avec une déclaration éventuelle comme:
{ "$set": {
"events.0.handled": 0,
"events.2.handled": 0
}}
Encore une fois, le problème ici est la "présomption" que ces valeurs d'index trouvées lors de la lecture du document sont les mêmes valeurs d'index dans le tableau au moment de la mise à jour. Si de nouveaux éléments sont ajoutés au tableau d'une manière qui modifie l'ordre, ces positions ne sont plus valides et les mauvais éléments sont en fait mis à jour.
Donc, jusqu'à ce qu'il y ait une syntaxe raisonnable déterminée pour permettre le traitement de plusieurs éléments de tableau correspondants dans une seule instruction de mise à jour, l'approche de base consiste à mettre à jour chaque élément de tableau correspondant dans une instruction individuelle (idéalement en masse) ou à déterminer essentiellement les éléments de tableau maximum pour mettre à jour ou continuer à mettre à jour jusqu'à ce qu'aucun résultat modifié ne soit renvoyé. Dans tous les cas, vous devez "toujours" traiter les mises à jour de position$
sur l'élément de tableau correspondant, même si cela ne met à jour qu'un élément par instruction.
Les opérations en masse sont en fait la solution "généralisée" pour traiter toutes les opérations qui se révèlent être des "opérations multiples", et comme il y a plus d'applications pour cela que la simple mise à jour de plusieurs éléments de tableau avec la même valeur, elle a bien sûr été implémentée déjà, et c'est actuellement la meilleure approche pour résoudre ce problème.