MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Aggiornamento di un array annidato con MongoDB

Ambito generale e spiegazione

Ci sono alcune cose che non vanno in quello che stai facendo qui. In primo luogo le condizioni della query. Ti riferisci a diversi _id valori in cui non dovresti averne bisogno e almeno uno dei quali non è al livello più alto.

Per entrare in un valore "nidificato" e presumere anche quel _id il valore è univoco e non apparirà in nessun altro documento, il modulo di query dovrebbe essere così:

Model.update(
    { "array1.array2._id": "123" },
    { "$push": { "array1.0.array2.$.answeredBy": "success" } },
    function(err,numAffected) {
       // something with the result in here
    }
);

Ora funzionerebbe davvero, ma in realtà è solo un colpo di fortuna perché ci sono ottime ragioni per cui non dovrebbe funzionare per te.

La lettura importante è nella documentazione ufficiale per il posizionale $ operatore sotto l'argomento "Nested Arrays". Ciò che dice è:

L'operatore posizionale $ non può essere utilizzato per query che attraversano più di un array, ad esempio query che attraversano array nidificati all'interno di altri array, perché la sostituzione del segnaposto $ è un valore singolo

In particolare, ciò significa che l'elemento che verrà confrontato e restituito nel segnaposto posizionale è il valore dell'indice dal primo matrice corrispondente. Ciò significa nel tuo caso l'indice corrispondente nell'array di livello "superiore".

Quindi, se guardi la notazione della query come mostrata, abbiamo "codificato" il primo (o 0 index ) posizione nell'array di livello superiore, e succede che l'elemento corrispondente all'interno di "array2" sia anche la voce di indice zero.

Per dimostrarlo puoi cambiare il _id corrispondente valore a "124" e il risultato sarà $push una nuova voce sull'elemento con _id "123" poiché sono entrambi nella voce di indice zero di "array1" e questo è il valore restituito al segnaposto.

Quindi questo è il problema generale con gli array di annidamento. Potresti rimuovere uno dei livelli e saresti ancora in grado di $push all'elemento corretto nell'array "top", ma ci sarebbero comunque più livelli.

Cerca di evitare di annidare gli array poiché incontrerai problemi di aggiornamento come mostrato.

Il caso generale è "appiattire" le cose che "pensi" siano "livelli" e in realtà rendere queste "attributi" sugli elementi di dettaglio finali. Ad esempio, la forma "appiattita" della struttura nella domanda dovrebbe essere qualcosa del tipo:

 {
   "answers": [
     { "by": "success", "type2": "123", "type1": "12" }
   ]
 }

O anche quando si accetta l'array interno è $push solo e mai aggiornato:

 {
   "array": [
     { "type1": "12", "type2": "123", "answeredBy": ["success"] },
     { "type1": "12", "type2": "124", "answeredBy": [] }
   ]
 }

Entrambi si prestano ad aggiornamenti atomici nell'ambito del $ posizionale operatore

MongoDB 3.6 e versioni successive

Da MongoDB 3.6 sono disponibili nuove funzionalità per lavorare con gli array nidificati. Questo utilizza il filtro posizionale $[<identifier>] sintassi per abbinare gli elementi specifici e applicare condizioni diverse tramite arrayFilters nella dichiarazione di aggiornamento:

Model.update(
  {
    "_id": 1,
    "array1": {
      "$elemMatch": {
        "_id": "12","array2._id": "123"
      }
    }
  },
  {
    "$push": { "array1.$[outer].array2.$[inner].answeredBy": "success" }
  },
  {
    "arrayFilters": [{ "outer._id": "12" },{ "inner._id": "123" }] 
  }
)

Il "arrayFilters" come passato alle opzioni per .update() o anche.updateOne() , .updateMany() , .findOneAndUpdate() o .bulkWrite() Il metodo specifica le condizioni da abbinare all'identificatore fornito nell'istruzione di aggiornamento. Tutti gli elementi che soddisfano la condizione indicata verranno aggiornati.

Poiché la struttura è "nidificata", in realtà utilizziamo "filtri multipli" come specificato con una "matrice" di definizioni di filtri come mostrato. L'"identificatore" contrassegnato viene utilizzato nella corrispondenza con il filtro posizionale $[<identifier>] sintassi effettivamente utilizzata nel blocco di aggiornamento dell'istruzione. In questo caso inner e outer sono gli identificatori utilizzati per ogni condizione come specificato con la catena nidificata.

Questa nuova espansione rende possibile l'aggiornamento del contenuto dell'array nidificato, ma in realtà non aiuta con la praticità di "interrogare" tali dati, quindi si applicano le stesse avvertenze spiegate in precedenza.

In genere "intendi" esprimere come "attributi", anche se il tuo cervello inizialmente pensa "annidamento", di solito è solo una reazione a come credi che le "parti relazionali precedenti" si uniscano. In realtà hai davvero bisogno di più denormalizzazione.

Vedi anche Come aggiornare più elementi di array in mongodb, poiché questi nuovi operatori di aggiornamento corrispondono e aggiornano effettivamente "elementi multipli di array" anziché solo il primo , che è stata l'azione precedente degli aggiornamenti posizionali.

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

Tuttavia questo non è vero per il mongo shell, dal momento che il metodo è implementato lì ("ironicamente per compatibilità con le versioni precedenti") gli arrayFilters argomento non viene riconosciuto e rimosso da un metodo interno che analizza le opzioni al fine di fornire "compatibilità con le versioni precedenti" con le versioni precedenti del server MongoDB e un .update() "legacy" Sintassi delle chiamate API.

Quindi, se vuoi usare il comando nel mongo shell o altri prodotti "shell based" (in particolare Robo 3T) è necessaria una versione più recente dal ramo di sviluppo o dalla versione di produzione a partire dalla 3.6 o successiva.

Vedi anche positional all $[] che aggiorna anche "elementi multipli dell'array" ma senza applicarsi a condizioni specificate e si applica a tutti elementi nell'array in cui è l'azione desiderata.