243 votes

MongoDB - Met à jour un objet dans un tableau imbriqué

Supposons que nous disposons de la collection suivante, j'ai quelques questions à propos de:

{
    "_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 - je veux augmenter le prix pour "item_name":"my_item_two" et si il n'existe pas, il doit être annexé à les "éléments" de la matrice.

2 - Comment puis-je mettre à jour deux champs en même temps. Par exemple, augmenter le prix pour "my_item_three" et en même temps d'augmenter le "total" (avec la même valeur).

Je préfère le faire sur le MongoDB côté, sinon, je dois charger le document dans le côté client (Python) et de construire le document mis à jour et de le remplacer avec celui existant dans MongoDB.

Mise à JOUR C'est ce que j'ai essayé et qui fonctionne bien SI L'Objet Existe :

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

Mais si la clé n'existe pas, il ne fait rien. Aussi, il ne met à jour l'objet imbriqué. Il n'y a aucune manière avec cette commande pour mettre à jour le "total" de terrain.

276voto

matulef Points 776

Pour la question #1, nous allons le diviser en deux parties. Tout d'abord, incrément de tout document qui a "des éléments.item_name" égal à "my_item_two". Pour cela, vous devrez utiliser la position "$" de l'opérateur. Quelque chose comme:

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

Notez que cela n'augmente le premier appariés sous-document dans n'importe quel tableau (donc si vous avez un autre document dans le tableau avec "item_name" égal à "my_item_two", il ne sera pas incrémenté). Mais c'est peut-être ce que vous voulez.

La deuxième partie est plus délicate. Nous pouvons pousser un nouvel élément à un tableau sans un "my_item_two" comme suit:

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

Pour votre question #2, la réponse est plus facile. Pour incrémenter le total et le prix de item_three dans tout document qui contient des "my_item_three", vous pouvez utiliser le $inc opérateur sur plusieurs champs en même temps. Quelque chose comme:

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

27voto

Udit Points 26

Il n'y a aucun moyen de faire cela en une seule requête. Vous devez rechercher le document dans la première requête:

Si le document existe:

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

Autre

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

Pas besoin d'ajouter la condition {$ne : "my_item_two" } .

De plus, dans l'environnement multithread, vous devez veiller à ce qu'un seul thread puisse exécuter le second (insérer une casse si le document n'a pas été trouvé) à la fois, sinon des documents incorporés dupliqués seront insérés.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X