Na parte anterior deste tutorial sobre MongoDB para iniciantes em NoSQL, falei sobre os conceitos mais elementares deste banco de dados, sobre quando usar e quando não usar esta tecnologia, quais as principais diferenças dele para outros bancos de dados e deixamos o servidor e o cliente prontos para receber comandos, sendo que inclusive executamos alguns para testar tudo.
Se não fez a primeira parte do tutorial, faça, no mínimo a seção final onde configuramos o ambiente. Continuaremos exatamente de onde a última parte parou.
Nesta segunda parte, falaremos dos comandos elementares (CRUD) do MongoDB. Veremos:
Caso prefira assistir a um vídeo ao invés de ler o longo post, o vídeo abaixo é uma aula gratuita do meu curso de Node.js e MongoDB. Aproveite!
Vamos lá!
#1 – Insert “Avançado”
Na seção anterior aprendemos a fazer um find() que retorna todos os documentos de uma coleção e um insert que insere um novo documento em uma coleção, além de outros comandos menores. Agora vamos adicionar mais alguns registros no seu terminal cliente mongo:
1 2 3 4 |
> custArray = [{ nome : "Fernando", idade : 29 }, { nome : "Teste", "uf" : "RS" }] > db.customers.insertMany(custArray) |
Atenção: para o nome dos campos dos seus documentos e até mesmo para o nome das coleções do seu banco, use o padrão de nomes de variáveis JS (camel-case, sem acentos, sem espaços, etc).
Nota: no exemplo acima a variável custArray passa a existir durante toda a sessão do terminal a partir do comando seguinte.
Nesse exemplo passei um array com vários documentos para nossa função insertMany inserir na coleção customers e isso nos trás algumas coisas interessantes para serem debatidas. Primeiro, sim, você pode passar um array de documentos por parâmetro para o insertMany. Segundo, você notou que o segundo documento não possui “idade”? E que ele possui um campo “uf”?
#2 – Find “avançado”
Para se certificar que todos documentos foram realmente inseridos na coleção, use o seguinte comando:
1 2 3 |
> db.customers.find().pretty() |
É o mesmo comando find() que usamos anteriormente, mas com a função pretty() no final para identar o resultado da função no terminal, ficando mais fácil de ler. Use e você vai notar a diferença, principalmente em consultas com vários resultados.
Mas voltando à questão do “uf”, ao contrário dos bancos relacionais, o MongoDB possui schema variável, ou seja, se somente um customer tiver “uf”, somente ele terá esse campo, não existe um schema pré-definido compartilhado entre todos os documentos, cada um é independente. Obviamente considerando que eles compartilham a mesma coleção, é interessante que eles possuam coisas em comum, caso contrário não faz sentido guardar eles em uma mesma coleção.
Mas como fica isso nas consultas? E se eu quiser filtrar por “uf”? Não tem problema!
Essa é uma boa deixa para eu mostrar como filtrar um find() por um campo do documento:
1 2 3 |
> db.customers.find({uf: "RS"}) |
Note que a função find pode receber um documento por parâmetro representando o filtro a ser aplicado sobre a coleção para retornar documentos. Nesse caso, disse ao find que retornasse todos os documentos que possuam o campo uf definido como “RS”. O resultado no seu terminal deve ser somente o customer de nome “Teste” (não vou falar do _id dele aqui pois o valor muda completamente de um servidor MongoDB para outro).
Atenção: MongoDB é case-sensitive ao contrário dos bancos relacionais, então cuidado!
Experimente digitar outros valores ao invés de “RS” e verá que eles não retornam nada, afinal, não basta ter o campo uf, ele deve ser exatamente igual a “RS”.
Além de campos com valores específicos, esse parâmetro do find permite usar uma infinidade de operadores como por exemplo, trazer todos documentos que possuam a letra ‘a’ no nome:
1 2 3 |
> db.customers.find({nome: /a/i }) |
Se você já mexeu com expressões regulares (regex) em JS antes, sabe exatamente como usar e o poder desse recurso junto a um banco de dados, sendo um equivalente muito mais poderoso ao LIKE dos bancos relacionais.
Mas e se eu quiser trazer todos os customers maiores de idade?
1 2 3 |
> db.customers.find({idade: {$gte: 18}}) |
O operador $gte (Greater Than or Equal) retorna todos os documentos que possuam o campo idade e que o valor do mesmo seja igual ou superior à 18. E podemos facilmente combinar filtros usando vírgulas dentro do documento passado por parâmetro, assim como fazemos quando queremos inserir campos em um documento:
1 2 3 |
> db.customers.find({nome: "Luiz", idade: {$gte: 18}}) |
O que a expressão acima irá retornar?
Se você disse customers cujo nome sejam Luiz e que sejam maiores de idade, você acertou!
E a expressão abaixo:
1 2 3 |
> db.customers.find({nome: { $regex: /a/ }, idade: {$gte: 18}}) |
Customers cujo nome contenham a letra ‘a’ e que sejam maiores de idade, é claro!
Outros operadores que você pode usar junto ao filtro do find são:
- $eq: exatamente igual (=)
- $ne: diferente (<> ou !=)
- $gt: maior do que (>)
- $lt: menor do que (<)
- $lte: menor ou igual a (<=)
- $in: o valor está contido em um array de possibilidades, como em um OU. Ex: {idade: {$in: [10,12] }}
- $all: MongoDB permite campos com arrays. Ex: { tags: [“NodeJS”, “MongoDB”] }. Com esse operador, você compara se seu campo multivalorado possui todos os valores de um array específico. Ex: {tags: {$all: [“NodeJS”, “Android”]}}
Entre outros!
Você também pode usar findOne ao invés de find para retornar apenas o primeiro documento, ou ainda as funções limit e skip para limitar o número de documentos retornados e para ignorar alguns documentos, especificamente, da seguinte maneira:
1 2 3 |
> db.customers.find().skip(1).limit(10) |
No exemplo acima retornaremos 10 customers ignorando o primeiro existente na coleção.
E para ordenar? Usamos a função sort no final de todas as outras, com um documento indicando quais campos e se a ordenação por aquele campo é crescente (1) ou descrescente (-1), como abaixo em que retorno todos os customers ordenados pela idade:
1 2 3 |
> db.customers.find().sort({idade: 1}) |
Nota: assim como nos bancos relacionais, os métodos de consulta retornam em ordem de chave primária por padrão, o que neste caso é o _id.
Ok, vimos como usar o find de maneiras bem interessantes e úteis, mas e os demais comandos de manipulação do banco?
#3 – Update
Além do insert que vimos antes, também podemos atualizar documentos já existentes, sendo que o jeito mais simples (e burro) de atualizar um documento é chamando a função replaceOne na coleção com 2 parâmetros:
- filtro para saber qual(is) documento(s) será(ão) atualizado(s);
- novo documento que substituirá o antigo;
Como em:
1 2 3 |
> db.customers.replaceOne({nome: "Luiz"}, {nome: "Luiz", idade: 29, uf: "RS"}) |
Como resultado você deve ter um modifiedCount maior do que 1, mostrando quantos documentos foram atualizados.
Por que essa é a maneira mais burra de fazer um update? Porque além de perigosa ela exige que você passe o documento completo a ser atualizado no segundo parâmetro, pois ele substituirá o original!
Primeira regra do update inteligente: se você quer atualizar um documento apenas, comece usando updateOne ao invés de replaceOne. O updateOne vai te obrigar a usar operadores ao invés de um documento inteiro para a atualização, o que é muito mais seguro.
Segunda regra do update inteligente: sempre que possível, use a chave primária (_id) como filtro da atualização, pois ela é sempre única dentro da coleção.
Terceira regra do update inteligente: evite usar updateMany e outras formas de atualizar vários registros ao mesmo tempo.
Mas que operadores de update são esses?
Assim como o find possui operadores de filtro, o update possui operadores de atualização. Se eu quero, por exemplo, mudar apenas o nome de um customer, eu não preciso enviar todo o documento do respectivo customer com o nome alterado, mas sim apenas a expressão de alteração do nome, como abaixo (já usando o _id como filtro, que é mais seguro):
1 2 3 |
> db.customers.updateOne({_id: ObjectId("59ab46e433959e2724be2cbd")}, {$set: {idade: 28}}) |
Nota: para saber o _id correto do seu update, faça um find primeiro e não tente copiar o meu pois não vai repetir.
Esta função vai alterar (operador $set) a idade para o valor 28 do documento cujo _id seja “59ab46e433959e2724be2cbd” (note que usei uma função ObjectId para converter, pois esse valor não é uma string).
Nota: você pode usar null se quiser “limpar” um campo.
O operador $set recebe um documento contendo todos os campos que devem ser alterados e seus respectivos novos valores. Qualquer campo do documento original que não seja indicado no set continuará com os valores originais.
Atenção: se o campo a ser alterado não existir no documento, ele será criado.
Não importa o valor que ela tenha antes, o operador $set vai sobrescrevê-lo. Agora, se o valor anterior importa, como quando queremos incrementar o valor de um campo, não se usa o operador $set, mas sim outros operadores. A lista dos mais úteis operadores de update estão abaixo:
- $unset: remove o respectivo campo do documento;
- $inc: incrementa o valor original do campo com o valor especificado;
- $mul: multiplica o valor original do campo com o valor especificado;
- $rename: muda o nome do campo para o nome especificado;
Além disso, existe um terceiro parâmetro oculto no update que são as opções de update. Dentre elas, existe uma muito interessante do MongoDB: upsert, como abaixo:
1 2 3 |
> db.customers.updateOne({nome: "LuizTools"}, {$set: {uf: "RS"}}, {upsert: true}) |
Um upsert é um update como qualquer outro, ou seja, vai atualizar o documento que atender ao filtro passado como primeiro parâmetro, porém, se não existir nenhum documento com o respectivo filtro, ele será inserido, como se fosse um insert.
upsert = update + insert
Eu já falei como amo esse banco de dados? 😀
#4 – Delete
Pra encerrar o nosso conjunto de comandos mais elementares do MongoDB falta o delete, ops, deleteOne e deleteMany na verdade. Tem também o remove, que possui mais opções, mas é pouco usado.
Existe uma função deleteOne e uma deleteMany, o que a essa altura do campeonato você já deve saber a diferença. Além disso, assim como o find e o update, o primeiro parâmetro do remove é o filtro que vai definir quais documentos serão deletados e todos os operadores normais do find são aplicáveis.
Sendo assim, de maneira bem direta:
1 2 3 |
> db.customers.deleteOne({nome: "Luiz"}) |
Vai excluir todos os clientes cujo nome seja igual a “Luiz”.
Simples, não?!
Obviamente existem coisas muito mais avançadas do que esse rápido tópico de MongoDB. Lhe encorajo a dar uma olhada no site oficial do banco de dados onde há a seção de documentação, vários tutoriais e até mesmo a possibilidade de tirar certificações online para garantir que você realmente entendeu a tecnologia.
Na terceira parte deste tutorial, você verá como fazer backup e restaurar bases de dados MongoDB!
Curtiu o post? Que tal aprender a usar MongoDB com Node.js? Então clica no banner abaixo e dá uma conferida no meu curso!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.
Boa Luiz, bom post novamente. Aprendendo um pouco de Mongo por aqui. Seguinte, duas notas:
1. No exemplo do
upsert
, corrijaupdate
no lugar deupdateOne
, aparentemente como você mesmo mencionou, o upsert é só pro update – pelo menos pra mim geraError: the update operation document must contain atomic operators
caso tento usar com updateOne;2. Não sei se a documentação mudou recentemente ou o que, mas o comando
delete
não rolou, e testando no chute vi que é oremove
. Otherwise, geraTypeError: db.customers.delete is not a function
.Eras isso, valeu pelo post! Abraços.
1. É updateOne mesmo (embora funcione com o update também), mas por algum motivo bizarro me esqueci de colocar o $set. Já resolvi.
2. é remove, eu sempre cometo este mesmo erro de escrever ‘delete’…
Obrigado pelos apontamentos!
Boa, valeu! É nóis. =]