MongoDB - Aggiorna oggetti nell'array di un documento (aggiornamento nidificato)


204

Supponiamo di avere la seguente raccolta, di cui ho alcune domande:

{
    "_id" : ObjectId("4faaba123412d654fe83hg876"),
    "user_id" : 123456,
    "total" : 100,
    "items" : [
            {
                    "item_name" : "my_item_one",
                    "price" : 20
            },
            {
                    "item_name" : "my_item_two",
                    "price" : 50
            },
            {
                    "item_name" : "my_item_three",
                    "price" : 30
            }
    ]
}

1 - Voglio aumentare il prezzo per "item_name": "my_item_two" e se non esiste , dovrebbe essere aggiunto alla matrice "items".

2 - Come posso aggiornare due campi contemporaneamente. Ad esempio, aumentare il prezzo per "my_item_three" e contemporaneamente aumentare il "totale" (con lo stesso valore).

Preferisco farlo sul lato MongoDB, altrimenti devo caricare il documento sul lato client (Python) e costruire il documento aggiornato e sostituirlo con quello esistente in MongoDB.

AGGIORNAMENTO Questo è quello che ho provato e funziona bene SE L'oggetto esiste :

db.test_invoice.update({user_id : 123456 , "items.item_name":"my_item_one"} , {$inc: {"items.$.price": 10}})

Ma se la chiave non esiste non fa nulla. Inoltre aggiorna solo l'oggetto nidificato. Non è possibile con questo comando aggiornare anche il campo "totale".


1
Penso che non puoi farlo in Mongo, tranne forse con molto dolore usando l'Eval. Mongo è molto limitato nelle operazioni sui dati.
Antti Haapala,

3
@Haapala: mongodb ha $ inc e si aggiorna con upsert
jdi

1
@jdi sì sì, ma non aiuta molto qui, ma ciò di cui ha bisogno è più $ inc, condizionatamente, e se l'articolo non esiste, allora è necessario un $ push.
Antti Haapala,

Risposte:


237

Per la domanda n. 1, dividiamolo in due parti. Innanzitutto, incrementa qualsiasi documento con "items.item_name" uguale a "my_item_two". Per questo dovrai usare l'operatore "$" posizionale. Qualcosa di simile a:

 db.bar.update( {user_id : 123456 , "items.item_name" : "my_item_two" } , 
                {$inc : {"items.$.price" : 1} } , 
                false , 
                true);

Si noti che ciò incrementerà solo il primo documento secondario corrispondente in qualsiasi array (quindi se si dispone di un altro documento nell'array con "item_name" uguale a "my_item_two", non verrà incrementato). Ma questo potrebbe essere quello che vuoi.

La seconda parte è più complicata. È possibile inviare un nuovo elemento in un array senza "my_item_two" come segue:

 db.bar.update( {user_id : 123456, "items.item_name" : {$ne : "my_item_two" }} , 
                {$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
                false , 
                true);

Per la tua domanda n. 2, la risposta è più semplice. Per aumentare il totale e il prezzo di item_three in qualsiasi documento che contenga "my_item_three", è possibile utilizzare l'operatore $ inc su più campi contemporaneamente. Qualcosa di simile a:

db.bar.update( {"items.item_name" : {$ne : "my_item_three" }} ,
               {$inc : {total : 1 , "items.$.price" : 1}} ,
               false ,
               true);

Grazie per la risposta. La risposta per la domanda n. 2 è perfetta e funziona bene :) Ma per la domanda n. 1, il problema è che dovresti cercare il documento per "user_id" e non per "items.item_name". In questo caso non posso usare l'operatore posizionale, poiché non ho specificato l'array nella query di ricerca. Inoltre, voglio che entrambe le parti vengano eseguite in un colpo solo. (se possibile)
Majid

Bei esempi. Mi sembrava di rendere evidente questa direzione dai collegamenti nella mia risposta, ma continuavo a ricevere resistenza dicendo che non era quello che stava cercando. Indovina l'OP solo necessario per vedere esempi su come fare più $ inc.
jdi,

Per la domanda n. 1, dovresti comunque riuscire a farlo in due parti aggiungendo l'id_utente al bit di query di ciascuno degli aggiornamenti (modificherò di conseguenza la risposta). Dovrà pensare di più se è possibile fare in un solo colpo.
matulef

6
Quali sono i parametri 3 ° e 4 ° nel metodo di aggiornamento? e perché?
skmahawar,

5
@skmahawar per quanto riguarda il 3o e il 4o parametro, docs.mongodb.com/manual/reference/method/db.collection.update indica che queste opzioni sono rispettivamente per "upsert" e "multi". Per upsert, se impostato su true, crea un nuovo documento quando nessun documento corrisponde ai criteri della query. Il valore predefinito è false, che non inserisce un nuovo documento quando non viene trovata alcuna corrispondenza. "Per multi, se impostato su true, aggiorna più documenti che soddisfano i criteri di query. Se impostato su false, aggiorna un documento. Il valore predefinito è falso
Mike Strand,

24

Non è possibile farlo in una singola query. Devi cercare il documento nella prima query:

Se il documento esiste:

db.bar.update( {user_id : 123456 , "items.item_name" : "my_item_two" } , 
                {$inc : {"items.$.price" : 1} } , 
                false , 
                true);

Altro

db.bar.update( {user_id : 123456 } , 
                {$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
                false , 
                true);

Non è necessario aggiungere condizioni {$ne : "my_item_two" }.

Anche nell'ambiente multithreading devi fare attenzione che solo un thread possa eseguire il secondo (inserire il caso, se il documento non è stato trovato) alla volta, altrimenti verranno inseriti documenti di embed duplicati.


1
no, c'è un modo per farlo in una sola query, vedi sopra
Martijn Scheffer,

1
@MartijnScheffer, non vedo un modo per farlo come una singola query ovunque in questo thread. Anche la "risposta" sta usando 2 query come in questa risposta. Mi sto perdendo qualcosa? Spero anche che ci sia un modo per farlo in una query per evitare problemi di multi threading
dferraro

@Udit, come posso usare gli articoli. $. Prezzo all'interno della condizione? quando provo ad aggiungere una condizione come "increment solo se il prezzo è maggiore di 0", non funziona - praticamente nulla viene aggiornato. Posso usarlo nella condizione in cui mi manca o mi manca qualcosa? oggetti. $. prezzo: {$ gt: 0}
dferraro

qualcosa deve essere sparito :)
Martijn Scheffer,

3

Possiamo usare l' $setoperatore per aggiornare l'array nidificato all'interno dell'oggetto archiviato aggiornare il valore

db.getCollection('geolocations').update( 
   {
       "_id" : ObjectId("5bd3013ac714ea4959f80115"), 
       "geolocation.country" : "United States of America"
   }, 
   { $set: 
       {
           "geolocation.$.country" : "USA"
       } 
    }, 
   false,
   true
);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.