arrays - ejemplos - mongodb tutorial español
Cómo actualizar varios elementos de matriz en mongodb (9)
Con el lanzamiento de MongoDB 3.6 (y disponible en la rama de desarrollo de MongoDB 3.5.12) ahora puede actualizar múltiples elementos de la matriz en una única solicitud.
Utiliza la sintaxis de operador de actualización de $[<identifier>]
posicional filtrada introducida en esta versión:
db.collection.update(
{ "events.profile":10 },
{ "$set": { "events.$[elem].handled": 0 } },
{ "arrayFilters": [{ "elem.profile": 10 }], "multi": true }
)
Los "arrayFilters"
como se pasaron a las opciones para el .update()
o incluso .updateOne()
, .updateMany()
, .findOneAndUpdate()
o .bulkWrite()
, especifican las condiciones para que coincidan en el identificador proporcionado en la declaración de actualización. Cualquier elemento que coincida con la condición dada se actualizará.
Observando que el "multi"
como se da en el contexto de la pregunta se usó con la expectativa de que esto "actualizaría varios elementos", pero este no era y aún no es el caso. Su uso aquí se aplica a "documentos múltiples" como siempre ha sido el caso o ahora se especifica como la configuración obligatoria de .updateMany()
en las versiones de API modernas.
NOTA Irónicamente, dado que esto se especifica en el argumento "opciones" para
.update()
y métodos similares, la sintaxis generalmente es compatible con todas las versiones recientes del controlador de versión.Sin embargo, esto no es cierto para
mongo
shell, ya que la forma en que se implementa el método ("irónicamente por compatibilidad con versiones anteriores") el argumentoarrayFilters
no es reconocido y eliminado por un método interno que analiza las opciones para ofrecer "compatibilidad hacia atrás" con versiones de servidor MongoDB anteriores y una sintaxis de llamada de API.update()
"heredada".Por lo tanto, si desea utilizar el comando en el shell
mongo
u otros productos "basados en shell" (especialmente Robo 3T), necesita una versión más reciente de la rama de desarrollo o de la versión de producción a partir de 3.6 o superior.
Ver también positional all $[]
que también actualiza "múltiples elementos de matriz" pero sin aplicar a las condiciones especificadas y se aplica a todos los elementos en la matriz donde esa es la acción deseada.
Consulte también la Actualización de una matriz anidada con MongoDB sobre cómo estos nuevos operadores posicionales se aplican a las estructuras de matriz "anidadas", donde "las matrices están dentro de otras matrices".
Tengo un documento de Mongo que contiene una variedad de elementos.
Me gustaría restablecer el atributo .handled
de todos los objetos en la matriz donde .profile
= XX.
El documento está en la siguiente forma:
{
"_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": "....."
}
...
]
}
entonces, intenté lo siguiente:
.update({"events.profile":10},{$set:{"events.$.handled":0}},false,true)
Sin embargo, solo actualiza el primer elemento de matriz coincidente en cada documento. (Ese es el comportamiento definido para $ - el operador posicional ).
¿Cómo puedo actualizar todos los elementos del conjunto combinado?
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 a una sintaxis clara que admite "todos los casos" donde coinciden los conjuntos mutiple encontró. De hecho, ya existen métodos que "ayudan" en las soluciones a 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 solo 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 solo un elemento matemático en la matriz para cada documento en ese único declaración.
La mejor solución posible en la actualidad es encontrar y recorrer todos los documentos coincidentes y procesar las actualizaciones masivas, que al menos permitirán enviar muchas operaciones en una única solicitud con una respuesta singular. Opcionalmente, puede usar .aggregate()
para reducir el contenido de la matriz devuelto en el resultado de 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 parte .aggregate()
allí funcionará cuando haya un identificador "único" para la matriz o todo el contenido de cada elemento forme un elemento "único". Esto se debe al operador "set" en $setDifference
utilizado para filtrar cualquier valor false
devuelto por la operación $map
utilizada para procesar la matriz de coincidencias.
Si el contenido de su 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"
}
}}
])
La limitación es que si "manejado" fue de hecho un campo destinado a estar presente en otros niveles de documentos, es probable que obtenga resultados no esperados, pero está bien cuando ese campo aparece solo en una posición de documento y es una coincidencia de igualdad.
Los lanzamientos futuros (post 3.1 MongoDB) al momento de la escritura tendrán una operación $filter
que es 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 con $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, se trata simplemente de elegir un enfoque e iterar los resultados con el mismo bloque de código que se muestra para procesar las instrucciones de actualización masiva. Las operaciones masivas y los "cursores" de salida agregada se introducen en la misma versión (MongoDB 2.6) y, por lo tanto, suelen trabajar mano a mano para el procesamiento.
Incluso en versiones anteriores, probablemente sea mejor utilizar .find()
para devolver el cursor, y filtrar la ejecución de las sentencias solo a la cantidad de veces que el elemento de la matriz se corresponde con las iteraciones .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 }});
});
});
Si está completamente 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 la cantidad máxima de combinaciones de arreglos posibles y simplemente ejecutar una actualización "múltiple" que muchos veces, hasta que básicamente no haya más documentos para actualizar.
Un enfoque válido para las versiones de MongoDB 2.4 y 2.2 también podría usar .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 })
}
En cualquier caso, hay ciertas cosas que no desea hacer dentro de la actualización:
No actualice "una sola vez" la matriz: dónde, si cree que podría ser más eficiente actualizar todo el contenido de la matriz en el código y luego solo
$set
toda la matriz 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
sigue siendo 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 se produzca entre lectura y escritura.No calcules los valores de índice para actualizar: donde similar al enfoque de "un solo disparo", obvias que la posición
0
y la posición2
(y así sucesivamente) son los elementos para actualizar y codificar estos con una declaración eventual como:{ "$set": { "events.0.handled": 0, "events.2.handled": 0 }}
De nuevo, 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 en el momento de la actualización. Si se agregan nuevos elementos a la matriz de una manera que cambie 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 varios elementos de matriz coincidentes se procesen en una única declaración de actualización, 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áxima para actualizar o mantener la actualización hasta que no se devuelvan más resultados modificados. En cualquier caso, debe "siempre" procesar 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 funcione como "operaciones múltiples", y dado que hay más aplicaciones para esto que simplemente actualizar elementos mutiples de matriz con el mismo valor, entonces, por supuesto, se ha implementado ya, y actualmente es el mejor enfoque para resolver este problema.
En este momento, no es posible usar el operador posicional para actualizar todos los elementos en una matriz. Ver JIRA http://jira.mongodb.org/browse/SERVER-1243
Como un trabajo a tu alrededor puedes:
- Actualice cada elemento individualmente (events.0.handled events.1.handled ...) o ...
- Lea el documento, realice las ediciones manualmente y guárdelo reemplazando el anterior (marque "Actualizar si es actual" si desea asegurarse de que haya actualizaciones atómicas)
En realidad, el comando guardar solo está en la instancia de la clase de documento. Eso tiene muchos métodos y atributos. Entonces puede usar la función lean () para reducir la carga de trabajo. Consulte aquí. https://hashnode.com/post/why-are-mongoose-mongodb-odm-lean-queries-faster-than-normal-queries-cillvawhq0062kj53asxoyn7j
Otro problema con la función de guardar, que hará que entren en conflicto los datos con varias guardas al mismo tiempo. Model.Update hará que los datos sean consistentes. Así que para actualizar múltiples elementos en el conjunto de documentos. Use su lenguaje de programación familiar y pruebe algo como esto, yo uso la mangosta en eso:
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))
})
Esto también se puede lograr con un ciclo while que verifica si queda algún documento que aún tenga subdocumentos que no se hayan actualizado. Este método preserva la atomicidad de sus actualizaciones (que muchas de las otras soluciones aquí no).
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 }
);
}
El número de veces que se ejecuta el bucle será igual al número máximo de veces que se presenten subdocumentos con un profile
igual a 10 y handled
no igual a 0 en cualquiera de los documentos de su colección. Entonces, si tiene 100 documentos en su colección y uno de ellos tiene tres subdocumentos que coinciden con la query
y todos los demás documentos tienen menos subdocumentos coincidentes, el ciclo se ejecutará tres veces.
Este método evita el peligro de dañar otros datos que pueden ser actualizados por otro proceso mientras se ejecuta este script. También minimiza la cantidad de datos que se transfieren entre el cliente y el servidor.
Estoy sorprendido de que esto todavía no haya sido abordado en mongo. En general, mongo no parece ser excelente cuando se trata de sub-arrays. No puede contar sub-arrays simplemente por ejemplo.
Usé la primera solución de Javier. Lea la matriz en eventos, luego repita y cree el conjunto exp:
var set = {}, i, l;
for(i=0,l=events.length;i<l;i++) {
if(events[i].profile == 10) {
set[''events.'' + i + ''.handled''] = 0;
}
}
.update(objId, {$set:set});
Esto se puede resumir en una función utilizando una devolución de llamada para la prueba condicional
Intenté lo siguiente y está funcionando bien.
.update({''events.profile'': 10}, { ''$set'': {''events.$.handled'': 0 }},{ safe: true, multi:true }, callback function);
// función de devolución de llamada en caso de nodejs
Lo que funcionó para mí fue esto:
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);
});
Creo que es más claro para mongo novatos y cualquier persona familiarizada con JQuery y amigos.
Solo quería agregar otra solución que funcionó para mí y es bastante sencilla. Aquí solo hay una serie de etiquetas (cadenas), por lo tanto, para actualizar una etiqueta llamada "prueba" a "cambiar", solo haga esto:
myDocuments.find({tags: "test" }, {fields: {_id: 1}}).forEach(function (doc) {
myDocuments.update(
{_id: doc._id, tags: "test"},
{$set:{''tags.$'': "changed"}});
});