Banco de Dados

3 out, 2017

MongoDB para iniciantes em NoSQL – Parte 02

Publicidade

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.

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:

> custArray = [{ nome : "Fernando", idade : 29 }, { nome : "Teste", "uf" : "RS" }]
> db.customers.insert(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 seção do terminal a partir do comando seguinte.

Nesse exemplo passei um array com vários documentos para nossa função insert 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 insert. Segundo, você notou que o segundo documento não possui “idade”? E que ele possui um campo “uf”?

Find “avançado”

Para se certificar de que todos os documentos foram realmente inseridos na coleção, use o seguinte comando:

> 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 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:

> 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:

> db.customers.find({nome: { $regex: /a/ }})

Se você já mexeu com expressões regulares (regex) em JS antes, sabe exatamente como usar e conhece 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?

> 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:

> db.customers.find({nome: "Luiz", idade: {$gte: 18}})

O que a expressão acima irá retornar?

Se você disse customers, cujo os nomes sejam “Luiz” e que sejam maiores de idade, você acertou!

E a expressão abaixo:

> db.customers.find({nome: { $regex: /a/ }, idade: {$gte: 18}})

Customers cujo os nomes contenham a letra ‘a’ e que sejam maiores de idade, é claro!

Outros operadores que você pode usar junto ao filtro do find são:

  • $e: 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:

> 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 decrescente (-1), como abaixo, em que retorno todos os customers ordenados pela idade:

> 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?

Update

Além do insert que vimos antes, também podemos atualizar documentos já existentes, por exemplo, usando o comando update e derivados. O jeito mais simples (e mais burro) de atualizar um documento, é chamando a função update na coleção com dois parâmetros:

  • documento de filtro para saber qual(is) documento(s) será(ão) atualizado(s);
  • novo documento que substituirá o antigo;

Como em:

> db.customers.update({nome: "Luiz"}, {nome: "Luiz", idade: 29, uf: "RS"})

Como resultado, você deve ter um nModified 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 apenas um documento, comece usando “updateOne” ao invés de “update”. 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: sempre use operadores ao invés de documentos inteiros no segundo parâmetro, independente do número de documentos que serão atualizados.

Mas que operadores 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 somente a expressão de alteração do nome, como abaixo (já usando o _id como filtro, que é mais seguro):

> 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.

Essa 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: o operador $set não adiciona campos novos em um documento, somente altera valores de campos já existentes.

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. Entre elas, existe uma muito interessante do MongoDB: upsert, como abaixo:

> db.customers.updateOne({nome: "LuizTools"}, {nome: "LuizTools", 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?

Delete

Pra encerrar o nosso conjunto de comandos mais elementares do MongoDB, falta o delete.

Existe uma função delete e uma deleteOne, 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 delete é 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:

> db.customers.delete({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 e última parte deste tutorial de MongoDB para iniciantes, falarei de modelagem de bancos de dados orientados à documentos. Aguarde!