web-dev-qa-db-fra.com

MongoDB - Mettre à jour des objets dans le tableau d'un document (mise à jour imbriquée)

Supposons que nous ayons la collection suivante, sur laquelle j'ai quelques questions:

{
    "_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 souhaite augmenter le prix de "item_name": "my_item_two" et s'il n'existe pas, il convient de l'ajouter au tableau "items".

2 - Comment puis-je mettre à jour deux champs en même temps? Par exemple, augmentez le prix de "my_item_three" et augmentez simultanément le "total" (avec la même valeur).

Je préfère effectuer cela du côté de MongoDB, sinon je dois charger le document dans le côté client (Python), construire le document mis à jour et le remplacer par celui qui existe dans MongoDB.

UPDATE C'est ce que j'ai essayé et 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, cela ne fait rien. En outre, il ne met à jour que l'objet imbriqué. Il n’existe aucun moyen avec cette commande de mettre à jour le champ "total" également.

187
Majid

Pour la question n ° 1, divisons la en deux parties. Commencez par incrémenter tout document dont "items.item_name" est égal à "my_item_two". Pour cela, vous devrez utiliser l'opérateur de position "$". 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'incrémentera que le premier sous-document correspondant dans un tableau (ainsi, si vous avez un autre document dans le tableau avec "nom_article" é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 compliquée. Nous pouvons pousser un nouvel élément dans un tableau sans "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 n ° 2, la réponse est plus facile. Pour incrémenter le total et le prix de item_three dans tout document contenant "my_item_three", vous pouvez utiliser l'opérateur $ inc sur plusieurs champs à la fois. Quelque chose comme:

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

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

Else

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" }.

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

23
Udit