Questo in effetti si riferisce al problema di vecchia data su http://jira.mongodb.org/browse/SERVER-1243 dove ci sono in realtà una serie di sfide a una chiara sintassi che supporta "tutti i casi" in cui le corrispondenze di array multipli sono trovato. Esistono infatti già metodi che "aiutano" a risolvere questo problema, come le operazioni in blocco che sono state implementate dopo questo post originale.
Non è ancora possibile aggiornare più di un singolo elemento array corrispondente in una singola istruzione di aggiornamento, quindi anche con un aggiornamento "multi" tutto ciò che sarai in grado di aggiornare è solo un elemento matematico nell'array per ciascun documento in quel singolo dichiarazione.
La migliore soluzione possibile al momento è quella di trovare ed eseguire il ciclo di tutti i documenti corrispondenti ed elaborare gli aggiornamenti in blocco che consentiranno almeno di inviare molte operazioni in una singola richiesta con una risposta singolare. È possibile utilizzare facoltativamente .aggregate()
per ridurre il contenuto dell'array restituito nei risultati della ricerca a quelli che corrispondono alle condizioni per la selezione dell'aggiornamento:
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()
parte lì funzionerà quando esiste un identificatore "univoco" per l'array o tutto il contenuto per ciascun elemento forma un elemento "unico" stesso. Ciò è dovuto all'operatore "set" $setDifference
utilizzato per filtrare tutti i false
valori restituiti $map
dall'operazione utilizzata per elaborare l'array per le corrispondenze.
Se il contenuto dell'array non ha elementi univoci, puoi provare un approccio alternativo con $redact
:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
Il limite è che se "gestito" fosse in realtà un campo destinato ad essere presente ad altri livelli del documento, probabilmente si otterranno risultati inattesi, ma va bene dove quel campo appare solo in una posizione del documento ed è una corrispondenza di uguaglianza.
Le versioni future (post 3.1 MongoDB) al momento della scrittura avranno $filter
un'operazione più semplice:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$project": {
"events": {
"$filter": {
"input": "$events",
"as": "event",
"cond": { "$eq": [ "$$event.handled", 1 ] }
}
}
}}
])
E tutte le versioni che supportano .aggregate()
possono utilizzare il seguente approccio $unwind
, ma l'uso di quell'operatore lo rende l'approccio meno efficiente a causa dell'espansione dell'array nella pipeline:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$unwind": "$events" },
{ "$match": { "events.handled": 1 } },
{ "$group": {
"_id": "$_id",
"events": { "$push": "$events" }
}}
])
In tutti i casi in cui la versione MongoDB supporta un "cursore" dall'output aggregato, si tratta solo di scegliere un approccio e iterare i risultati con lo stesso blocco di codice mostrato per elaborare le istruzioni di aggiornamento collettivo. Le operazioni in blocco e i "cursori" dall'output aggregato sono introdotti nella stessa versione (MongoDB 2.6) e pertanto di solito lavorano insieme per l'elaborazione.
Anche nelle versioni precedenti è probabilmente meglio usare solo .find()
per restituire il cursore e filtrare l'esecuzione delle istruzioni solo per il numero di volte in cui l'elemento array viene abbinato per le .update()
iterazioni:
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 sei assolutamente determinato a fare aggiornamenti "multi" o ritieni che ciò sia in definitiva più efficiente dell'elaborazione di più aggiornamenti per ciascun documento abbinato, puoi sempre determinare il numero massimo di possibili corrispondenze di array ed eseguire un aggiornamento "multi" che molti volte, fino a quando praticamente non ci sono più documenti da aggiornare.
Un approccio valido per le versioni MongoDB 2.4 e 2.2 potrebbe anche utilizzare .aggregate()
per trovare questo valore:
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 ogni caso, ci sono alcune cose che non vuoi fare all'interno dell'aggiornamento:
Non aggiornare "un colpo" l'array: dove, se si ritiene che potrebbe essere più efficiente aggiornare l'intero contenuto dell'array nel codice e quindi solo $set
l'intero array in ciascun documento. Questo potrebbe sembrare più veloce da elaborare, ma non vi è alcuna garanzia che il contenuto dell'array non sia cambiato da quando è stato letto e viene eseguito l'aggiornamento. Sebbene $set
sia ancora un operatore atomico, aggiornerà l'array solo con ciò che "ritiene" siano i dati corretti, e quindi è probabile che sovrascriva eventuali cambiamenti che si verificano tra lettura e scrittura.
Non calcolare i valori dell'indice da aggiornare: Laddove simile all'approccio "one shot", devi solo capire che posizione 0
e posizione 2
(e così via) sono gli elementi per aggiornarli e codificarli con ed eventualmente un'istruzione come:
{ "$set": {
"events.0.handled": 0,
"events.2.handled": 0
}}
Ancora una volta il problema qui è la "presunzione" che quei valori dell'indice rilevati al momento della lettura del documento siano gli stessi valori dell'indice nell'array al momento dell'aggiornamento. Se nuovi elementi vengono aggiunti all'array in modo da modificare l'ordine, tali posizioni non sono più valide e gli articoli errati vengono infatti aggiornati.
Quindi fino a quando non viene determinata una sintassi ragionevole per consentire l'elaborazione di più elementi di array con corrispondenza in una singola istruzione di aggiornamento, l'approccio di base consiste nell'aggiornamento di ciascun elemento di matrice con corrispondenza in un'istruzione individuale (idealmente in blocco) o essenzialmente nell'elaborazione degli elementi di matrice massima per aggiornare o continuare l'aggiornamento fino a quando non vengono restituiti risultati più modificati. Ad ogni modo, dovresti "sempre" elaborare gli aggiornamenti posizionali$
sull'elemento array corrispondente, anche se si sta aggiornando solo un elemento per istruzione.
Le operazioni in blocco sono in realtà la soluzione "generalizzata" per l'elaborazione di qualsiasi operazione che si risolve in "operazioni multiple", e poiché ci sono più applicazioni per questo rispetto al semplice aggiornamento di elementi di array multipli con lo stesso valore, allora è stata ovviamente implementata già, ed è attualmente l'approccio migliore per risolvere questo problema.