MongoDB – Atualizar objects na matriz de um documento (atualização aninhada)

Suponha que temos a seguinte coleção, sobre a qual tenho poucas perguntas:

{ "_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 – Eu quero aumentar o preço para “item_name”: “my_item_two” e se ele não existir , ele deve ser anexado à matriz “items”.

2 – Como posso atualizar dois campos ao mesmo tempo? Por exemplo, aumente o preço para “my_item_three” e ao mesmo tempo aumente o “total” (com o mesmo valor).

Eu prefiro fazer isso no lado do MongoDB, caso contrário eu tenho que carregar o documento no lado do cliente (Python) e construir o documento atualizado e substituí-lo pelo existente no MongoDB.

ATUALIZAÇÃO Isso é o que eu tentei e funciona bem se o object existe :

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

Mas se a chave não existe, não faz nada. Também atualiza apenas o object nested. Não há como esse comando atualizar o campo “total” também.

Para a questão # 1, vamos dividi-lo em duas partes. Primeiro, incremente qualquer documento que tenha “items.item_name” igual a “my_item_two”. Para isso você terá que usar o operador posicional “$”. Algo como:

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

Note que isto irá apenas incrementar o primeiro subdocumento correspondente em qualquer array (então se você tiver outro documento na matriz com “item_name” igual a “my_item_two”, ele não será incrementado). Mas isso pode ser o que você quer.

A segunda parte é mais complicada. Podemos enviar um novo item para um array sem um “my_item_two” como 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); 

Para sua pergunta # 2, a resposta é mais fácil. Para incrementar o total e o preço de item_three em qualquer documento que contenha “my_item_three”, você pode usar o operador $ inc em vários campos ao mesmo tempo. Algo como:

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

Não há como fazer isso em uma única consulta. Você tem que procurar o documento na primeira consulta:

Se o documento existir:

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

Outro

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

Não há necessidade de adicionar condição {$ne : "my_item_two" } .

Também em ambientes multithread, você deve ter cuidado para que apenas um thread possa executar o segundo (inserir caso, se o documento não foi encontrado) por vez, caso contrário, documentos duplicados serão inseridos.