De fato, isso se relaciona com a questão de longa data em http://jira.mongodb.org/browse/SERVER-1243, na qual existem de fato vários desafios a uma sintaxe clara que suporta "todos os casos" em que há várias correspondências de matriz. encontrado. De fato, já existem métodos que "ajudam" em soluções para esse problema, como Operações em Massa que foram implementadas após esta postagem original.
Ainda não é possível atualizar mais de um elemento de matriz combinada em uma única instrução de atualização, portanto, mesmo com uma atualização "multi", tudo o que você poderá atualizar é apenas um elemento matemático na matriz para cada documento naquele único declaração.
A melhor solução possível no momento é encontrar e fazer o loop de todos os documentos correspondentes e processar as atualizações em massa que permitirão pelo menos que muitas operações sejam enviadas em uma única solicitação com uma resposta única. Opcionalmente, você pode usar .aggregate()
para reduzir o conteúdo da matriz retornado no resultado da pesquisa apenas para aqueles que correspondem às condições da seleção de atualização:
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();
A .aggregate()
parte funcionará quando houver um identificador "exclusivo" para a matriz ou todo o conteúdo de cada elemento formar um elemento "exclusivo". Isso ocorre devido ao operador "set" $setDifference
usado para filtrar quaisquer false
valores retornados da $map
operação usada para processar a matriz para correspondências.
Se o conteúdo da sua matriz não tiver elementos exclusivos, você pode tentar uma abordagem alternativa com $redact
:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
Onde a limitação é que, se "manipulado" era de fato um campo destinado a estar presente em outros níveis de documento, é provável que você obtenha resultados inesperados, mas é bom que esse campo apareça apenas em uma posição de documento e corresponda à igualdade.
Versões futuras (MongoDB post 3.1) até o momento da gravação terão uma $filter
operação mais simples:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$project": {
"events": {
"$filter": {
"input": "$events",
"as": "event",
"cond": { "$eq": [ "$$event.handled", 1 ] }
}
}
}}
])
E todas as versões com suporte .aggregate()
podem usar a seguinte abordagem $unwind
, mas o uso desse operador torna a abordagem menos eficiente devido à expansão da matriz no pipeline:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$unwind": "$events" },
{ "$match": { "events.handled": 1 } },
{ "$group": {
"_id": "$_id",
"events": { "$push": "$events" }
}}
])
Em todos os casos em que a versão do MongoDB suporta um "cursor" da saída agregada, é apenas uma questão de escolher uma abordagem e iterar os resultados com o mesmo bloco de código mostrado para processar as instruções de atualização em massa. Operações em massa e "cursores" da saída agregada são introduzidos na mesma versão (MongoDB 2.6) e, portanto, geralmente trabalham lado a lado no processamento.
Em versões ainda anteriores, provavelmente é melhor usar apenas .find()
para retornar o cursor e filtrar a execução das instruções apenas o número de vezes que o elemento da matriz corresponde às .update()
iterações:
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 }});
});
});
Se você está determinado a fazer atualizações "múltiplas" ou considera-as mais eficientes do que processar várias atualizações para cada documento correspondente, sempre pode determinar o número máximo de correspondências possíveis de matriz e apenas executar uma atualização "múltipla" que muitos vezes, até que basicamente não haja mais documentos para atualizar.
Uma abordagem válida para as versões MongoDB 2.4 e 2.2 também pode ser usada .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 })
}
Seja qual for o caso, há certas coisas que você não deseja fazer na atualização:
Não "atualize" a matriz: onde, se você acha que seria mais eficiente atualizar todo o conteúdo da matriz no código e apenas $set
a matriz inteira em cada documento. Isso pode parecer mais rápido de processar, mas não há garantia de que o conteúdo da matriz não tenha sido alterado desde que foi lido e a atualização foi realizada. Embora $set
ainda seja um operador atômico, ele somente atualizará a matriz com o que "pensa" como sendo os dados corretos e, portanto, provavelmente substituirá quaisquer alterações que ocorram entre leitura e gravação.
Não calcule os valores do índice para atualizar: onde semelhante à abordagem "one shot", você apenas elabora que posição 0
e posição 2
(e assim por diante) são os elementos para atualizar e codificá-los com uma declaração eventual como:
{ "$set": {
"events.0.handled": 0,
"events.2.handled": 0
}}
Novamente, o problema aqui é a "presunção" de que esses valores de índice encontrados quando o documento foi lido são os mesmos valores de índice na matriz no momento da atualização. Se novos itens forem adicionados à matriz de uma maneira que altere a ordem, essas posições não serão mais válidas e os itens errados serão de fato atualizados.
Portanto, até que exista uma sintaxe razoável determinada para permitir que vários elementos da matriz correspondidos sejam processados na instrução de atualização única, a abordagem básica é atualizar cada elemento da matriz correspondida em uma declaração individual (idealmente em massa) ou essencialmente calcular os elementos máximos da matriz para atualizar ou continuar atualizando até que nenhum resultado modificado seja retornado. De qualquer forma, você deve "sempre" processar atualizações posicionais$
no elemento de matriz correspondente, mesmo que isso esteja atualizando apenas um elemento por instrução.
As operações em massa são, de fato, a solução "generalizada" para processar qualquer operação que funcione como "várias operações" e, como existem mais aplicativos para isso do que apenas atualizar elementos de matriz múltiplos com o mesmo valor, é claro que ela foi implementada já, e atualmente é a melhor abordagem para resolver esse problema.