Domanda

Ho un Mongo documento che contiene un array di elementi.

Vorrei ripristinare il .handled attributo di tutti gli oggetti dell'array in cui .profile = XX.

Il documento è il seguente modulo:

{
    "_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": "....."
        }
        ...
    ]
}

così, ho provato le seguenti:

.update({"events.profile":10},{$set:{"events.$.handled":0}},false,true)

Tuttavia si aggiorna solo il prima abbinato elemento di un array in ogni documento.(Che è il comportamento definito per $ - posizionale operatore.)

Come posso aggiornare tutti corrispondenza di elementi di un array?

È stato utile?

Soluzione

In questo momento non è possibile utilizzare l'operatore posizionale per aggiornare tutti gli elementi di un array. Vedere JIRA http://jira.mongodb.org/browse/SERVER-1243

Come un lavoro intorno è possibile:

  • Aggiornare ciascun elemento singolarmente (Events.0.handled events.1.handled ...) o di ...
  • Leggi il documento, fare le modifiche manualmente e salvarla sostituendo la uno più vecchio (verificare "Update se Corrente " se si vuole garantire aggiornamenti atomiche)

Altri suggerimenti

Che cosa ha funzionato per me è stato questo:

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);
  });

Credo che sia più chiaro per i neofiti mongo e chiunque abbia familiarità con JQuery e gli amici.

Con il rilascio di MongoDB 3.6 (e disponibile in ramo di sviluppo da MongoDB 3.5.12) ora è possibile aggiornare più elementi della matrice in una singola richiesta.

Questo utilizza la sintassi operatore Filtered posizionale $[<identifier>] aggiornamento introdotta in questa versione:

db.collection.update(
  { "events.profile":10 },
  { "$set": { "events.$[elem].handled": 0 } },
  { "arrayFilters": [{ "elem.profile": 10 }], "multi": true }
)

Il "arrayFilters" come passato alle opzioni per .update() o anche .updateOne() , .updateMany() , .findOneAndUpdate() o .bulkWrite() metodo specifica le condizioni per abbinare sul identificativo indicato nella dichiarazione di aggiornamento. Tutti gli elementi che soddisfano la condizione data verranno aggiornati.

Rilevando che il "multi" come indicato nel contesto della questione è stata utilizzata nella speranza che ciò "aggiornare più elementi", ma questo non era e ancora non è il caso. E 'di utilizzo qui si applica al "più documenti" , come è sempre stato il caso o la società altrimenti specificato come impostazione obbligatorio di .updateMany() nelle versioni API moderni.

  

Nota Un po 'ironicamente, dal momento che questo è specificato nelle "opzioni" argomento per .update() e come i metodi, la sintassi è generalmente compatibile con tutte le versioni di driver rilascio recente.

     

Tuttavia questo non è vero del guscio mongo, in quanto la relativa metodo viene attuato lì ( "ironicamente per compatibilità all'indietro") l'argomento arrayFilters non viene riconosciuto e rimosso mediante un metodo interno che analizza le opzioni per consegnare " la compatibilità" con le versioni server MongoDB precedenti e una "sintassi di chiamata .update() API legacy".

     

Quindi, se si desidera utilizzare il comando nella shell mongo o altro "guscio a base" di prodotti (in particolare Robo 3T) è necessaria una versione più recente sia dal ramo di sviluppo o di rilascio in produzione a partire dal 3.6 o superiore.

Vedi anche positional all $[] che aggiorna anche "matrice multipla elementi" ma senza applicare a determinate condizioni, si applica alla tutti elementi della matrice in cui cioè l'azione desiderata.

Aggiornamento di un array nidificato con MongoDB per come questi nuovi operatori di posizione si applicano a strutture di matrice "annidate", dove "matrici sono all'interno di altre matrici".

  

Importante - installazioni eseguito l'aggiornamento da versioni precedenti "potrebbe" non hanno permesso a MongoDB dispone, che può anche causare dichiarazioni a fallire. Si dovrebbe garantire la vostra procedura di aggiornamento è completo con i dettagli come gli aggiornamenti di indice e quindi eseguire

   db.adminCommand( { setFeatureCompatibilityVersion: "3.6" } )
     

o una versione maggiore, come è applicabile alla vostra versione installata. cioè "4.0" per la versione 4 in poi attualmente. Ciò ha permesso tali caratteristiche come i nuovi operatori aggiornamento di posizione e altri. È inoltre possibile controllare con:

   db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )
     

Per ripristinare l'impostazione corrente

Questo può anche essere realizzato con un ciclo while, che consente di verificare se tutti i documenti rimangono che hanno ancora i documenti secondari che non sono stati aggiornati. Questo metodo consente di mantenere l'atomicità degli aggiornamenti (che molte delle altre soluzioni qui 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 }
    );
}

Il numero di volte che il ciclo viene eseguito sarà pari al numero massimo di volte documenti secondari con profile pari al 10 e handled non è uguale a 0 si verificano in uno dei documenti nella vostra collezione. Quindi, se si dispone di 100 documenti nella vostra collezione e uno di loro ha tre documenti secondari quella partita query e tutti gli altri documenti hanno un minor numero di documenti secondari corrispondenti, il ciclo verrà eseguito tre volte.

Questo metodo evita il pericolo di sovrascrivere altri dati che possono essere aggiornati da un altro processo, mentre Questo script viene eseguito. Si riduce al minimo la quantità di dati trasferiti tra client e server.

Questa in effetti si riferisce alla lunga problema in piedi a http://jira.mongodb.org / browse / SERVER-1243 dove ci sono infatti una serie di sfide per una chiara sintassi che sostiene "tutti i casi" in cui si trovano le partite matrice mutiple. Ci sono in realtà metodi già in atto che "aiuti" in soluzioni a questo problema, come Operazioni collettivo che sono state implementate dopo questo post originale.

Non è ancora possibile aggiornare più di un singolo elemento dell'array abbinati in un unico prospetto di aggiornamento, quindi anche con un "multi" aggiornare tutto ciò che sarà mai in grado di aggiornamento è solo un elemento Mathed nella matrice per ogni documento in quella sola istruzione.

La migliore soluzione possibile al momento è quello di trovare e loop di tutti i documenti abbinati e aggiornamenti processo di massa, che saranno almeno consentono molte operazioni da inviare in una singola richiesta con una risposta singolare. Opzionalmente si può usare .aggregate() per ridurre il contenuto di matrice restituita in risultato della ricerca per solo quelli che soddisfano le condizioni per la selezione di 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 porzione .aggregate() lì funziona quando v'è un identificatore "unico" per la matrice o tutto il contenuto di ciascun elemento forma un elemento "unico" stesso. Ciò è dovuto al l'operatore "set" in $setDifference utilizzato per filtrare i valori false restituiti dall'operazione $map utilizzato per elaborare la matrice per le partite.

Se il contenuto di matrice non ha elementi unici si può 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"
        }
    }}
])

dove è limitazione è che se "maneggiato" era in realtà un campo destinato a essere presente in altri livelli del documento, allora è probabile che sta per ottenere risultati unexepected, ma è bene dove quel campo appare solo in una posizione del documento ed è una uguaglianza partita.

Le versioni future (inviare 3.1 MongoDB) come della scrittura avrà un'operazione $filter che è più semplice:

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$project": {
        "events": {
            "$filter": {
                "input": "$events",
                "as": "event",
                "cond": { "$eq": [ "$$event.handled", 1 ] }
            }
        }
    }}
])

E tutte le emissioni che il sostegno .aggregate() possono utilizzare il seguente approccio con $unwind , ma l'utilizzo di tale operatore rende l'approccio meno efficiente a causa dell'espansione dell'array in cantiere:

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" di produzione aggregata, allora questa è solo una questione di scelta di un approccio e iterando i risultati con lo stesso blocco di codice mostrato per elaborare le istruzioni di aggiornamento collettivo. Operazioni sfuso e "cursori" di produzione aggregata vengono introdotti nella stessa versione (MongoDB 2.6) e quindi di solito lavorano fianco a fianco per la lavorazione.

Nelle versioni precedenti, anche allora è probabilmente meglio solo uso .find() per riportare il cursore, e il filtro a termine l'esecuzione di istruzioni da solo il numero di volte che l'elemento di matrice è abbinato per le iterazioni .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 }});
    });
});

Se si aboslutely determinati a fare gli aggiornamenti "multi" o ritengono che, per essere in ultima analisi, più efficiente di elaborazione più aggiornamenti per ogni documento abbinato, allora si può sempre determinare il numero massimo di possibili corrispondenze matrice e solo eseguire un "multi" aggiornamento che molte volte, fino a quando in pratica non ci sono più documenti da aggiornare.

Un approccio valido per MongoDB 2.4 e 2.2 versioni potrebbe anche usare .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 si fanno non vuole fare entro l'aggiornamento:

  1. non "one shot" aggiornare la matrice: Dove se si pensa che potrebbe essere più efficiente per aggiornare l'intero contenuto array in codice e poi basta $set l'intero array in ogni documento . Questo potrebbe sembrare più veloce al processo, ma non v'è alcuna garanzia che il contenuto di matrice non è cambiato da quando è stato letto e viene eseguito l'aggiornamento. Sebbene $set è ancora un operatore atomico, sarà solo aggiornare la matrice con quello che "pensa" è i dati corretti, e quindi è probabile sovrascrivere eventuali cambiamenti tra lettura e scrittura.

  2. valori di indice non calcolano da aggiornare: Dove simile al "one shot" approccio si lavora solo che la posizione 0 e la posizione 2 (e così via) sono gli elementi di aggiornamento e il codice di questi in con ed eventuale dichiarazione come:

    { "$set": {
        "events.0.handled": 0,
        "events.2.handled": 0
    }}
    

    Anche qui il problema è la "presunzione" che tali valori di indice trovati quando il documento è stato letto sono gli stessi valori di indice di matrice th al momento del aggiornamento. Se i nuovi elementi vengono aggiunti alla matrice in un modo che cambia l'ordine poi quelle posizioni non sono più validi e gli articoli errati sono infatti aggiornati.

Quindi fino a quando c'è una sintassi ragionevole determinato per consentire più abbinati elementi dell'array da elaborare in istruzione di aggiornamento singolo quindi l'approccio di base è o aggiornamento ogni elemento dell'array abbinato in un'istruzione indvidual (idealmente in volume) o sostanzialmente elaborare la massime elementi dell'array di aggiornare o aggiornamento mantenere fino risultati più modificati vengono restituiti. In ogni caso, si dovrebbe "sempre" essere l'elaborazione di posizionale $ aggiornamenti sul l'elemento dell'array abbinato, anche se questo è solo un elemento di aggiornamento secondo il prospetto.

operazioni di massa sono in effetti la soluzione "generalizzata" per l'elaborazione di tutte le operazioni che opera ad essere "operazioni multiple", e poiché ci sono più applicazioni per questo che semplicemente aggiornando elementi di campo mutiple con lo stesso valore, allora è di Naturalmente stato attuato già, ed è attualmente il metodo migliore per risolvere questo problema.

Sono stupito questo ancora non è stato affrontato in Mongo. mongo Nel complesso non sembra essere grande quando si tratta di sub-array. Non si può contare sub-array semplicemente per esempio.

Ho usato prima soluzione di Javier. Leggi l'array in eventi quindi scorrere e costruire il set di 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});

Questo può essere estratta in una funzione utilizzando un callback per il test condizionale

Sono stato alla ricerca di una soluzione a questo utilizzando il driver più recente per C # 3.6 ed ecco la correzione alla fine ho optato per. La chiave qui sta usando "$ []" che secondo MongoDB è nuova a partire dalla versione 3.6. Vedere https: //docs.mongodb. com / / riferimento / operatore / aggiornamento manuale / posizionale-tutto / # fino. S [] per ulteriori informazioni.

Ecco il codice:

{
   var filter = Builders<Scene>.Filter.Where(i => i.ID != null);
   var update = Builders<Scene>.Update.Unset("area.$[].discoveredBy");
   var result = collection.UpdateMany(filter, update, new UpdateOptions { IsUpsert = true});
}

Per ulteriori contesto vedere il mio post originale qui: elemento rimuovi matrice da tutti i documenti che utilizzano MongoDB C conducente #

Ho provato quanto segue e il suo bel lavoro.

.update({'events.profile': 10}, { '$set': {'events.$.handled': 0 }},{ safe: true, multi:true }, callback function);

// richiamata la funzione in caso di nodejs

Il filo è molto vecchio, ma mi è venuto in cerca di risposta qui fornendo in tal modo la nuova soluzione.

Con MongoDB versione 3.6+, è ora possibile utilizzare l'operatore posizionale per aggiornare tutti gli elementi di un array. Vedere documentazione ufficiale qui .

A seguito di interrogazione avrebbe funzionato per la domanda posta qui. Ho anche verificato con il driver Java MongoDB e funziona con successo.

.update(   // or updateMany directly, removing the flag for 'multi'
   {"events.profile":10},
   {$set:{"events.$[].handled":0}},  // notice the empty brackets after '$' opearor
   false,
   true
)

Spero che questo aiuti qualcuno come me.

In realtà, Il comando salva è solo su istanza del Documento di classe.Che hanno un sacco di metodi e attributi.Quindi, è possibile utilizzare lean() la funzione di ridurre il carico di lavoro.Fare riferimento qui. https://hashnode.com/post/why-are-mongoose-mongodb-odm-lean-queries-faster-than-normal-queries-cillvawhq0062kj53asxoyn7j

Un altro problema con la funzione di salvataggio, che renderà i dati in conflitto con il multi-salva al tempo stesso.Modello.Aggiornamento farà i dati in modo coerente.Quindi, per aggiornare multi elementi nell'array di documento.Utilizzare il familiare linguaggio di programmazione e di provare qualcosa di simile, io uso mangusta che:

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))
})

$ [] seleziona operatore tutte array nidificato ..si può aggiornare tutti gli elementi di matrice con '$ []'

.update({"events.profile":10},{$set:{"events.$[].handled":0}},false,true)

Riferimento

Si prega di essere consapevole del fatto che alcune risposte in questa discussione che suggerisce l'uso $ [] è sbagliato.

db.collection.update(
   {"events.profile":10},
   {$set:{"events.$[].handled":0}},
   {multi:true}
)

Il codice sopra aggiornerà "trattati" a 0 per tutti gli elementi di matrice "eventi", indipendentemente dal suo valore "profilo". Il {"events.profile":10} query è solo per filtrare l'intero documento, non i documenti nella matrice. In questa situazione è un must per l'uso con $[elem] arrayFilters per specificare la condizione di elementi di matrice così la risposta di Neil Lunn è corretta.

Ho appena voluto aggiungere un'altra soluzione che ha funzionato per me ed è abbastanza semplice. Qui è solo una serie di tag (stringhe) in modo di aggiornare un tag chiamato "test" per "cambiato", basta fare questo:

myDocuments.find({tags: "test" }, {fields: {_id: 1}}).forEach(function (doc) {
    myDocuments.update(
        {_id: doc._id, tags: "test"}, 
        {$set:{'tags.$': "changed"}});
    });
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top