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.