MongoDB - Atualizar objetos na matriz de um documento (atualização aninhada)


204

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 - Quero aumentar o preço de "item_name": "my_item_two" e, se não existir , deve ser anexado à matriz "items".

2 - Como posso atualizar dois campos ao mesmo tempo. Por exemplo, aumente o preço de "my_item_three" e, ao mesmo tempo, aumente o "total" (com o mesmo valor).

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

ATUALIZAÇÃO Isto é o que eu tentei e funciona bem se o objeto 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 objeto aninhado. Não há como este comando atualizar o campo "total" também.


1
Eu acho que você não pode fazer isso no mongo, exceto talvez com muita dor usando o eval. O Mongo é muito limitado em operações de dados.
— Antti Haapala

3
@Haapala: mongodb tem $ inc e atualização com upsert
— jdi 9/12

1
@ jdi yes yes, mas isso não ajuda muito aqui, mas o que ele precisa é de vários $ incs, condicionalmente, e se o item não existir, é necessário um $ push.
— Antti Haapala

Respostas:


237

Para a pergunta 1, vamos dividir em duas partes. Primeiro, incremente qualquer documento que tenha "items.item_name" igual a "my_item_two". Para isso, você precisará usar o operador posicional "$". Algo como:

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

Observe que isso apenas incrementará o primeiro subdocumento correspondente em qualquer matriz (portanto, 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ê deseja.

A segunda parte é mais complicada. Podemos enviar um novo item para uma matriz sem um "my_item_two" da seguinte maneira:

 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 nº 2, a resposta é mais fácil. Para incrementar o total e o preço do 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);

Obrigado pela resposta. A resposta para a pergunta 2 é perfeita e funciona bem :) Mas para a pergunta 1, o problema é que você deve procurar o documento por "user_id" e não por "items.item_name". Nesse caso, não posso usar o Operador Posicional, pois não especifiquei a matriz na consulta de pesquisa. Também quero que as duas partes sejam feitas de uma só vez. (se possível)
— Majid

Bons exemplos. Eu senti como se estivesse tornando essa direção aparente a partir dos links na minha resposta, mas continuei tendo resistência dizendo que não era o que ele estava procurando. Acho que o OP só precisava ver exemplos de como fazer vários $ inc.
— Jdi

Para a pergunta 1, você ainda poderá fazer isso em duas partes, adicionando o user_id ao bit de consulta de cada uma das atualizações (modificarei a resposta de acordo). Terá que pensar mais sobre se é possível fazer de uma só vez.
— matulef

6
Quais são os terceiro e quarto parâmetros no método de atualização? e porque?
— Skmahawar # 27/15

5
@skmahawar em relação ao terceiro e quarto parâmetros, docs.mongodb.com/manual/reference/method/db.collection.update indica que essas opções são para "upsert" e "multi", respectivamente. Para upsert, se definido como true, cria um novo documento quando nenhum documento corresponde aos critérios de consulta. O valor padrão é false, que não insere um novo documento quando nenhuma correspondência é encontrada. "Para multi, se definido como true, atualiza vários documentos que atendem aos critérios de consulta. Se definido como false, atualiza um documento. O valor padrão é falsa.
— Mike Strand

24

Não há como fazer isso em uma única consulta. Você precisa pesquisar 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 no ambiente multithread, você deve tomar cuidado para que apenas um thread possa executar o segundo (inserir caixa, se o documento não for encontrado) por vez, caso contrário, serão inseridos documentos incorporados duplicados.


1
não, há uma maneira de fazer isso em uma única consulta, veja acima
— Martijn Scheffer

1
@ MartijnScheffer, não vejo uma maneira de fazer isso como uma única consulta em qualquer lugar deste segmento. Até a "resposta" está usando 2 consultas iguais às desta resposta. Estou esquecendo de algo? Eu também estou esperando há uma maneira de fazer isso em uma consulta para evitar quaisquer problemas de multi threading
— dferraro

@ Edit, como posso usar os itens. $. Price dentro da condição? quando tento adicionar uma condição como "incrementar apenas se o preço for maior que 0", ela não funciona - basicamente nada é atualizado. Posso usar isso na condição where ou estou perdendo alguma coisa? items. $. price: {$ gt: 0}
— dferraro 14/17

algo deve ter desapareceu :)
— Martijn Scheffer

3

Podemos usar o $setoperador para atualizar a matriz aninhada dentro do objeto arquivado, atualizar o valor

db.getCollection('geolocations').update( 
   {
       "_id" : ObjectId("5bd3013ac714ea4959f80115"), 
       "geolocation.country" : "United States of America"
   }, 
   { $set: 
       {
           "geolocation.$.country" : "USA"
       } 
    }, 
   false,
   true
);
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.