Atualizar elementos de array em um documento com operadores posicionais MQL
Avalie esse Tutorial
O MongoDB oferece uma linguagem de consulta avançada que é ótima para operações de criação, leitura, atualização e exclusão, bem como pipelines complexos de agregação de vários estágios. Há muitas maneiras de modelar seus dados no MongoDB e, independentemente de sua aparência, a MongoDB Query Language (MQL) ajuda você.
Uma das funcionalidades menos reconhecidas, mas extremamente valiosas do MQL, está nos operadores posicionais que você encontraria em uma operação de atualização.
Digamos que você tenha um documento e, dentro desse documento, tenha uma array de objetos. Você precisa atualizar um ou mais desses objetos na array, mas não deseja substituir a array ou anexar a ela. É aqui que um operador posicional pode ser valioso.
Neste tutorial, vamos ver alguns exemplos que se beneficiariam de um operador posicional no MongoDB.
Vamos usar o exemplo de que temos uma array em cada um de nossos documentos e queremos atualizar apenas a primeira correspondência dentro dessa array, mesmo que haja potencial para várias correspondências.
Para fazer isso, provavelmente queremos usar o operador
$
, que atua como um espaço reservado para atualizar o primeiro elemento correspondente.Para este exemplo, vamos usar um jogo eletrônico do tipo Pokemon. Dê uma olhada no seguinte documento do MongoDB:
1 { 2 "_id": "red", 3 "pokemon": [ 4 { 5 "number": 6, 6 "name": "Charizard" 7 } 8 { 9 "number": 25, 10 "name": "Pikachu", 11 }, 12 { 13 "number": 0, 14 "name": "MissingNo" 15 } 16 ] 17 }
Suponhamos que o documento acima represente as informações sobre o pokémon do jogo Pokémon Red. O documento não é um reflexo verdadeiro e está muito incompleto. No entanto, se você é Fã do jogo, provavelmente se lembrará do pokémon com falha chamado "MissingNo". Para criar uma história fictícia, vamos presumir que o desenvolvedor, em algum momento, queira dar um nome real a esse pokémon, mas se esqueceu.
Podemos atualizar esse elemento específico na array fazendo algo como o seguinte:
1 db.pokemon_game.update( 2 { "pokemon.name": "MissingNo" }, 3 { 4 "$set": { 5 "pokemon.$.name": "Agumon" 6 } 7 } 8 );
No exemplo acima, estamos fazendo um filtro para documentos que têm um elemento de array com um campo
name
definido como MissingNo
. Com o MongoDB, você não precisa especificar o índice da array em seu filtro para o operador update
. Na etapa de manipulação, estamos usando o operador posicional$
para alterar a primeira ocorrência da correspondência no filtro. Sim, no meu exemplo, estou renomeando o pokémon "MissingNo" para o de um Digimon, que é uma marca totalmente diferente.O novo documento ficaria assim:
1 { 2 "_id": "red", 3 "pokemon": [ 4 { 5 "number": 6, 6 "name": "Charizard" 7 } 8 { 9 "number": 25, 10 "name": "Pikachu", 11 }, 12 { 13 "number": 0, 14 "name": "Agumon" 15 } 16 ] 17 }
Se "MissingNo" aparecesse várias vezes na array, apenas a primeira ocorrência seria atualizada. Se "MissingNo" aparecesse várias vezes, mas os campos ao redor fossem diferentes, você poderia fazer a correspondência em vários campos usando o operador
$elemMatch
para restringir qual elemento específico deve ser atualizado.Digamos que você tenha uma array em seu documento e precise atualizar todos os elementos dessa array usando uma única operação. Para fazer isso, podemos dar uma olhada no operador
$[]
que faz exatamente isso.Usando o mesmo exemplo de videogame Pokémon, vamos imaginar que temos uma equipe de Pokémon e acabamos de terminar uma batalha no jogo. Os pontos de experiência ganhos na batalha precisam ser distribuídos para todos os Pokémon da sua equipe.
O documento que representa nossa equipe pode se parecer com o seguinte:
1 { 2 "_id": "red", 3 "team": [ 4 { 5 "number": 1, 6 "name": "Bulbasaur", 7 "xp": 5 8 }, 9 { 10 "number": 25, 11 "name": "Pikachu", 12 "xp": 32 13 } 14 ] 15 }
No final da partida, queremos garantir que cada pokémon da nossa equipe receba 10 experiência. Para fazer isso com o operador
$[]
, podemos construir uma operaçãoupdate
que se parece com o seguinte:1 db.pokemon_game.update( 2 { "_id": "red" }, 3 { 4 "$inc": { 5 "team.$[].xp": 10 6 } 7 } 8 );
No exemplo acima, usamos o modificador
$inc
para aumentar todos os campos xp
dentro da arrayteam
em um número constante. Para saber mais sobre o operador $inc
, consulte adocumentação.Nosso novo documento ficaria assim:
1 [ 2 { 3 "_id": "red", 4 "team": [ 5 { 6 "number": 1, 7 "name": "Bulbasaur", 8 "xp": 15 9 }, 10 { 11 "number": 25, 12 "name": "Pikachu", 13 "xp": 42 14 } 15 ] 16 } 17 ]
Embora seja útil para este exemplo, não fornecemos exatamente critérios para o caso de um de seus Pokémon não receber pontos de experiência. Se o seu Pokémon desmaiou, talvez ele não devesse receber o aumento.
Aprenderemos sobre filtros na próxima parte do tutorial.
Vamos usar o exemplo de que temos vários elementos de array que queremos atualizar em uma única operação e não queremos nos preocupar com código excessivo do lado do cliente emparelhado com uma operação de substituição.
Para fazer isso, provavelmente queremos usar o operador
$[<identifier>]
, que atua como um espaço reservado para atualizar todos os elementos que correspondem a uma condiçãoarrayFilters
.Para colocar as coisas em perspectiva, digamos que estamos lidando com cartas colecionáveis de Pokémon, em vez de videogames, e rastreando seus valores. Nossos documentos podem ter a seguinte aparência:
1 db.pokemon_collection.insertMany( 2 [ 3 { 4 _id: "nraboy", 5 cards: [ 6 { 7 "name": "Charizard", 8 "set": "Base", 9 "variant": "1st Edition", 10 "value": 200000 11 }, 12 { 13 "name": "Pikachu", 14 "set": "Base", 15 "variant": "Red Cheeks", 16 "value": 300 17 } 18 ] 19 }, 20 { 21 _id: "mraboy", 22 cards: [ 23 { 24 "name": "Pikachu", 25 "set": "Base", 26 "variant": "Red Cheeks", 27 "value": 300 28 }, 29 { 30 "name": "Pikachu", 31 "set": "McDonalds 25th Anniversary Promo", 32 "variant": "Holo", 33 "value": 10 34 } 35 ] 36 } 37 ] 38 );
É claro que o trecho acima não é um documento, mas uma operação para inserir dois documentos em alguma coleção
pokemon_collection
no MongoDB. No cenário acima, cada documento representa uma coleção de cartões para um individual. A arraycards
contém informações sobre o cartão na coleção, bem como o valor atual.Em nosso exemplo, precisamos atualizar os preços dos cartões, mas não queremos fazer um número X de operações de atualização no banco de dados. Queremos fazer apenas uma única operação para atualizar os valores de cada um dos nossos cartões.
Pegue a seguinte query:
1 db.pokemon_collection.update( 2 {}, 3 { 4 "$set": { 5 "cards.$[elemX].value": 350, 6 "cards.$[elemY].value": 500000 7 } 8 }, 9 { 10 "arrayFilters": [ 11 { 12 "elemX.name": "Pikachu", 13 "elemX.set": "Base", 14 "elemX.variant": "Red Cheeks" 15 }, 16 { 17 "elemY.name": "Charizard", 18 "elemY.set": "Base", 19 "elemY.variant": "1st Edition" 20 } 21 ], 22 "multi": true 23 } 24 );
A operação
update
acima é como qualquer outra, mas com uma etapa extra para o nosso operador posicional. O primeiro parâmetro, que é um objeto vazio, representa nossos critérios de correspondência. Como ele está vazio, estaremos atualizando todos os documentos da coleção.O próximo parâmetro é a manipulação que queremos fazer em nossos documentos. Vamos ignorá-lo por enquanto e ver o
arrayFilters
no terceiro parâmetro.Imagine que queremos atualizar o preço de dois cartões específicos que possam existir na coleção de pokémon de qualquer pessoa. Neste exemplo, queremos atualizar o preço dos cartões Pikachu e Chartizard. Se você é um filtro donos de coleções de cartas, você saberá que há muitas variações do cartão de Pikachu e Chartizard, então nos tornamos específicos em nossa array
arrayFilters
. Para cada objeto na array, os campos desses objetos representam uma condiçãoand
. Então, para elemX
, que não tem nenhuma convenção de nomenclatura específica, todos os três campos devem ser satisfeitos.No exemplo acima, estamos utilizando
elemX
e elemY
para representar dois filtros diferentes.Let's Go back to the second parameter in the operation
update
. Se o filtroelemX
retornar como verdadeiro porque houve correspondência com um item de matriz em um documento, o campo value
desse objeto será definido com um novo valor. Da mesma forma, o mesmo poderia acontecer com o filtroelemY
. Se um documento tiver uma matriz e um dos filtros nunca corresponder a um elemento dessa matriz, ele será ignorado.Se analisarmos o nosso exemplo, os documentos agora seriam os seguintes:
1 [ 2 { 3 "_id": "nraboy", 4 "cards": [ 5 { 6 "name": "Charizard", 7 "set": "Base", 8 "variant": "1st Edition", 9 "value": 500000 10 }, 11 { 12 "name": "Pikachu", 13 "set": "Base", 14 "variant": "Red Cheeks", 15 "value": 350 16 } 17 ] 18 }, 19 { 20 "_id": "mraboy", 21 "cards": [ 22 { 23 "name": "Pikachu", 24 "set": "Base", 25 "variant": "Red Cheeks", 26 "value": 350 27 }, 28 { 29 "name": "Pikachu", 30 "set": "McDonalds 25th Anniversary Promo", 31 "variant": "Holo", 32 "value": 10 33 } 34 ] 35 } 36 ]
Se qualquer array específica contiver várias correspondências para um dos critérios
arrayFilter
, todas as correspondências terão seu preço atualizado. Isso significa que, se eu tivesse, digamos, 100 cartões de Pikachu correspondentes em minha coleção de pokémon, todos os 100 agora teriam novos preços.Você acabou de ver como usar alguns dos operadores posicionais na linguagem de query do MongoDB (MQL). Esses operadores são úteis ao trabalhar com arrays, pois evitam que você tenha que fazer substituições completas na array ou manipulação estendida do lado do cliente.
Para saber mais sobre MQL, confira meu tutorial anterior intituladoGetting Started with Atlas and the MongoDB Query Language (MQL).