No último artigo que publiquei, apresentei um banco de dados de “Pokemons” com duas coleções: Pokemons e Combats. Inclusive, recomendo a leitura do artigo de agregação antes da leitura deste em específico.
Na coleção Pokemons, possuímos em torno de 800 documentos com os atributos de cada um dos pokémons, enquanto na coleção Combats é apresentado da seguinte maneira:
Antes da resolução desse problema, precisamos nos aprofundar no assunto, principalmente quando essa coleção foi criada. Concordamos aqui que existem outras formas de modelagem, certo? Existem diversas maneiras de arquitetar os dados desse database, e a forma como a coleção foi criada não foi pensada para operações que resolvam essa nossa problemática, fazendo com que tenhamos que utilizar o operador de agregação $lookup.
Vamos, então, entender o que faz esse operador, de acordo com a documentação do MongoDB.
Executa um left Join para uma coleção no mesmo banco de dados para filtrar documentos da coleção "unida" para processamento. O estágio $lookup adiciona um novo campo de matriz a cada documento de entrada, esse novo campo de matriz contém os documentos correspondentes da coleção "unida". O estágio $lookup passa esses documentos reformulados para o próximo estágio.
O $lookup tem a seguinte sintaxe:
O alerta fica, se você usa muito esse operador, é bastante provável que a modelagem desse banco poderia ter sido mais bem trabalhada. Fique atento às boas normas para quando você for construir a modelagem do seu banco:
1. Nesse caso dos combates, não seria mais fácil colocar o nome dos pokémons do que seus respectivos _id, ou por acaso teria uma maneira mais adequada de resolver essa questão?
No MongoDB, é possível inserir manualmente um valor específico ao id de um documento. No entanto, o MongoDB não impõe restrições nesse campo, o que significa que você pode inserir documentos com o mesmo id sem o MongoDB identificar um erro. E como podemos perceber, esse foi o método usado para construir a coleção combates, o _id foi inserido manualmente enquanto os documentos foram gerados.
E agora, chegamos a uma importante prática no MongoDB, que é o uso do ObjectId, no qual uma das vantagens é a garantia de unicidade. Além disso, o ObjectId possui uma propriedade de ordenação cronológica, o que pode ser útil em determinados cenários de ordenação por tempo. Então vamos lá, qual a diferença em deixar o combats com _id feito manualmente, e com ObjectId gerado pelo MongoDB?
O uso do ObjectId pode ser usado como referência de documentos, enquanto o id pode trazer problemas de duplicidade. Outro ponto importante seria Indexação, o ObjectId é otimizado para indexação no MongoDB, e isso significa que consultas que envolvem o campo id terão melhor desempenho.
Além do mais, o MongoDB trabalha muito bem com sub documentos, portanto, outra maneira ainda mais fácil (e correta) dentro das estruturas de boas práticas, é incorporar as informações relevantes do pokémon diretamente no documento de combate. Isso evita a necessidade de fazer a junção com outra coleção usando o $lookup. Perceba o exemplo abaixo:
Assim, incorporamos os atributos de cada um dos pokémons dentro dos combates.
RESOLUÇÃO DO PROBLEMA COM $LOOKUP
Voltando ao nosso problema, em como construir nosso pipeline para que o resultado seja, o nome dos pokémons que estão em combate, e o nome do vencedor.
Sabendo que queremos substituir o id pelo nome dos pokémons, vamos prosseguir com o $lookup, para o Firstpokemon e Second_pokemon:
Tanto o primeiro quanto o segundo $lookup são usados para combinar os documentos da coleção “combats” com os documentos correspondentes da coleção “pokemons” com base no First_pokemon, e Second_pokemon.
A continuação dessa agregação é a seguinte:
O primeiro $project é usado para remodelar como será a saída dos documentos. A expressão “$arrayElemAt” é usada para obter o primeiro elemento do array “pokemon1_arr” e “pokemon2_arr”, e armazenar em seu respectivo campo.
E o segundo $project ele vai procurar o “$pokemon1.name” e o “$second1.name”.
O winner é criado com base em uma condição. O valor do "winner" será definido como o campo "name" do "pokemon1". Caso contrário, o valor do campo "winner" será definido como o campo "name" do "pokemon2".
Trazendo o seguinte resultado:
E então, consegui te ajudar a entender sobre o $lookup? Muito bacana conhecer esse operador, afinal mesmo que não seja altamente recomendado, é bastante útil quando necessário usar.
REFERÊNCIAS
Udemy. Curso completo MongoDB 2022, Renan Palin. Disponível em < https://www.udemy.com/course/mongodb-curso-completo/>
Dataset Pokemon. Disponível em <https://www.kaggle.com/datasets/terminus7/pokemon-challenge>
Ressalto aqui, como no artigo anterior, a importância do curso do Renan Palin sobre o assunto de agregação.