De hecho, esto se relaciona con el problema de larga data en http://jira.mongodb.org/browse/SERVER-1243, donde de hecho hay una serie de desafíos para una sintaxis clara que admita "todos los casos" donde las coincidencias de matrices múltiples encontró. De hecho, ya existen métodos que "ayudan" en la solución de este problema, como las operaciones masivas que se han implementado después de esta publicación original.
Todavía no es posible actualizar más de un único elemento de matriz coincidente en una sola declaración de actualización, por lo que incluso con una actualización "múltiple", todo lo que podrá actualizar es un solo elemento coincidente en la matriz para cada documento en ese único declaración.
La mejor solución posible en este momento es encontrar y hacer un bucle de todos los documentos coincidentes y procesar actualizaciones masivas que permitirán al menos enviar muchas operaciones en una sola solicitud con una respuesta singular. Opcionalmente, puede usar .aggregate()
para reducir el contenido de la matriz devuelto en el resultado de la búsqueda a aquellos que coinciden con las condiciones para la selección de actualización:
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()
porción allí funcionará cuando haya un identificador "único" para la matriz o todo el contenido para cada elemento forme un elemento "único". Esto se debe a que el operador "set" se $setDifference
utiliza para filtrar los false
valores devueltos por la $map
operación utilizada para procesar la matriz en busca de coincidencias.
Si su contenido de matriz no tiene elementos únicos, puede probar un enfoque alternativo con $redact
:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
Donde está la limitación es que si "manejado" era de hecho un campo destinado a estar presente en otros niveles de documentos, entonces es probable que obtenga resultados inesperados, pero está bien donde ese campo aparece solo en una posición del documento y es una coincidencia de igualdad.
Las versiones futuras (posteriores a MongoDB 3.1) a partir de la escritura tendrán una $filter
operación más simple:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$project": {
"events": {
"$filter": {
"input": "$events",
"as": "event",
"cond": { "$eq": [ "$$event.handled", 1 ] }
}
}
}}
])
Y todas las versiones que admiten .aggregate()
pueden usar el siguiente enfoque $unwind
, pero el uso de ese operador lo convierte en el enfoque menos eficiente debido a la expansión de la matriz en la tubería:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$unwind": "$events" },
{ "$match": { "events.handled": 1 } },
{ "$group": {
"_id": "$_id",
"events": { "$push": "$events" }
}}
])
En todos los casos en que la versión de MongoDB admite un "cursor" de la salida agregada, entonces esto es solo una cuestión de elegir un enfoque e iterar los resultados con el mismo bloque de código que se muestra para procesar las declaraciones de actualización masiva. Las operaciones masivas y los "cursores" de la salida agregada se introducen en la misma versión (MongoDB 2.6) y, por lo tanto, generalmente funcionan de la mano para el procesamiento.
Incluso en versiones anteriores, entonces probablemente sea mejor usar solo .find()
para devolver el cursor y filtrar la ejecución de las declaraciones solo la cantidad de veces que el elemento de matriz coincide para las .update()
iteraciones:
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 está decidido a realizar actualizaciones "múltiples" o considera que en última instancia es más eficiente que procesar múltiples actualizaciones para cada documento coincidente, entonces siempre puede determinar el número máximo de posibles coincidencias de matriz y simplemente ejecutar una actualización "múltiple". veces, hasta que básicamente no hay más documentos para actualizar.
Un enfoque válido para las versiones MongoDB 2.4 y 2.2 también podría usarse .aggregate()
para encontrar este valor:
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 })
}
Cualquiera sea el caso, hay ciertas cosas que no desea hacer dentro de la actualización:
No actualice "una sola vez" la matriz: donde si cree que podría ser más eficiente actualizar todo el contenido de la matriz en código y luego solo $set
la matriz completa en cada documento. Esto puede parecer más rápido de procesar, pero no hay garantía de que el contenido de la matriz no haya cambiado desde que se leyó y se realizó la actualización. Aunque $set
todavía es un operador atómico, solo actualizará la matriz con lo que "cree" que son los datos correctos y, por lo tanto, es probable que sobrescriba cualquier cambio que ocurra entre lectura y escritura.
No calcule los valores de índice para actualizar: cuando sea similar al enfoque de "un disparo", simplemente calcule que la posición 0
y la posición 2
(y así sucesivamente) son los elementos para actualizar y codificar esto con una declaración eventual como:
{ "$set": {
"events.0.handled": 0,
"events.2.handled": 0
}}
Nuevamente, el problema aquí es la "presunción" de que los valores de índice encontrados cuando se leyó el documento son los mismos valores de índice en la matriz al momento de la actualización. Si se agregan nuevos elementos a la matriz de una manera que cambia el orden, esas posiciones ya no son válidas y, de hecho, los elementos incorrectos se actualizan.
Entonces, hasta que haya una sintaxis razonable determinada para permitir que se procesen múltiples elementos de matriz coincidentes en una sola declaración de actualización, entonces el enfoque básico es actualizar cada elemento de matriz coincidente en una declaración individual (idealmente en Bulk) o esencialmente resolver los elementos de matriz máximos para actualizar o seguir actualizando hasta que no se devuelvan más resultados modificados. En cualquier caso, debería "siempre" estar procesando actualizaciones posicionales$
en el elemento de matriz coincidente, incluso si eso solo está actualizando un elemento por declaración.
Las operaciones masivas son, de hecho, la solución "generalizada" para procesar cualquier operación que resulte ser "operaciones múltiples", y dado que hay más aplicaciones para esto que la simple actualización de elementos de matriz múltiple con el mismo valor, por supuesto, se ha implementado ya, y actualmente es el mejor enfoque para resolver este problema.