Usando o MongoDB – parte 2

mongo-2_abertura

A primeira parte teve uma rápida introdução sobre o que é o MongoDB e sua instalação (na máquina virtual), além de apresentado o MongoDB Shell junto com um exemplo daquilo que se pode fazer nele. Nesta parte a primeira metade do CRUD com a criação e recuperação de documentos.

E para ajudar a comparar, para cada operação no MongoDB uma execução análoga em banco de dados SQL — no caso o SQLite3.

Antes de começar

Já que modifiquei um pouco a configuração da máquina virtual e algumas outras coisas alguns passos antes de iniciar

$ cd ~/Vagrant/mongodb
$ git pull
...
$ git checkout parte-2
Switched to branch 'parte-2'
$ vagrant up --provision
Bringing machine 'default' up with 'virtualbox' provider...
...
==> default: Running provisioner: shell...
 default: Running: /tmp/vagrant-shell20180210-32662-149m2j3.py
==> default: Importados 45716 registro(s)
==> default: Running provisioner: shell...
 default: Running: /tmp/vagrant-shell20180210-32662-1d9mnmw.py
==> default: Importados 45716 registro(s)
$ vagrant ssh

Fiz uma ramificação¹ e incluí arquivos que farão algumas operações dentro da máquina virtual, daí o “- -provision” ao executar o Vagrant. Basicamente cuidarão da instalação do MongoDB, SQLite3 e do suporte do Python a eles; Também acrescentei programas para baixar e importar² nos dois gerenciadores o catálogo sobre quedas de meteoritos disponibilizado Open Data Portal da NASA.

ATENÇÃO : Em caso de erro na importação dos dados no SQLite3, apenas apague o arquivo “meteorites.sqlite3” e execute novamente o programa meteorites_import_sqlite3.py; no caso do MongoDB execute «mongo 127.0.0.1:27017/teste –eval “db.meteorites.drop();”» e depois rode meteorites_import_mongodb.py uma segunda vez.

(¹) Pois é! Adivinhem qual foi a minha surpresa ao descobrir que era possível fazer isto dentro de um Gist do GitHub? 😀

(²) Para facilitar o entendimento fiz um programa de importação distinto para cada banco de dados.

O tal do CRUD

Relembrando que CRUD. é o acrônimo para create, read, update & delete, ou seja, as ações de inserir, recuperar, atualizar e remover e que são como “quatro operações básicas” dentro de um banco de dados — e dos programas que armazenam informações em banco de dados.

As operações a seguir serão feitas diretamente dentro do MongoDB Shell e não diferem muito da forma como são executadas nas linguagens de programação onde o MongoDB é suportado.

$ mongo
MongoDB shell version v3.4.7
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.7
...
> use teste
switched to db teste

E antes de seguir adiante, estou usando o documento para fazer referência explícita ao que é armazenado nas coleções, apesar deles serem objetos JavaScript, para não criar confusão com outros objetos que são utilizados como parâmetros aos métodos do MongoDB.

Inserindo documentos

São métodos para inserção de documentos em coleções no MongoDB:

  • insert() — para inserir um ou mais documentos na coleção;
  • insertMany() — para a inserção de vários documentos, agrupados em um array, na coleção de uma só vez e
  • insertOne() — para inserir um único documento na coleção.

Os métodos insertMany() e insertOne() foram introduzidos na versão do 3.2 do MongoDB e são funcionalmente equivalentes ao insert():

> db.os.insertOne({name:'Ubuntu Linux',version:'17.10',kind:'Debian'})
{
 "acknowledged" : true,
 "insertedId" : ObjectId("5a8022fdb12a13075d7b09fe")
}
> db.oses.insert({name:'CentOS',version:'7.x',kind:'RedHat'})
WriteResult({ "nInserted" : 1 })

Em ambos os casos se obterá o mesmo resultado porém com diferença naquilo que é retornado por cada operação:

  • Os métodos insertOne() e insertMany() retornarão um objeto contendo as chaves “acknowledged” (relacionado com o write concern — que explicarei depois) e “insertedId” (contendo os valores das chaves únicas “_id” dos documentos inseridos) e
  • O método insert() retornará apenas a quantidade de registros inseridos nesta operação.

Aproveitando para lembrar que o SQL também permite que o comando INSERT faça a inserção de vários registros em uma única operação:

$ sqlite3 /vagrant/oses.sqlite3 -init /vagrant/oses.sql
SQLite version 3.19.3 2017-06-08 14:26:16
Enter ".help" for usage hints.
sqlite> INSERT INTO oses (name,version,kind) VALUES
   ...> ("Ubuntu Linux", "17.10", "Debian"),
   ...> ("CentOS","7.x","RedHat");

A diferença aqui é quase conceitual já que o SQL usa tuplas ao invés de um array de objetos.

Recuperando documentos

São métodos para a recuperação de documentos em uma coleção no MongoDB:

  • find() — Para recuperar da coleção todos os documentos que satisfaçam uma condição e
  • findOne() — Para recuperar apenas a primeira ocorrência.

Ambos os métodos possuem os seguintes objetos como parâmetros:

  1. Consulta (query) : Os critérios de pesquisa dos documentos. Se não estiver definido serão utilizados todos os documentos da coleção e
  2. Projeção (projection) : A relação das chaves que serão, ou não, exibidas. Estando vazia todas as chaves serão utilizadas.

Existem outros métodos que permitem recuperar documentos de uma coleção e ao mesmo tempo executar uma ação — apagar, atualizar ou substituir — mas preferi não tocar neles agora.

Recuperando todos os documentos.

Para recuperar os documentos de uma coleção, faça:

> db.oses.find().pretty()
{
        "_id" : ObjectId("5a8022fdb12a13075d7b09fe"),
        "name" : "Ubuntu Linux",
        "version" : "17.10",
        "kind" : "Debian"
}
{
        "_id" : ObjectId("5a8029b118b33e744978f190"),
        "name" : "CentOS",
        "version" : "7.x",
        "kind" : "RedHat"
}

O uso do método pretty() foi só para tornar mais legível a listagem dos documentos, caso contrário eles ocupariam uma linha cada (como poderá ser visto mais abaixo).

Seu análogo em SQL seria assim:

sqlite> SELECT * FROM oses;
1|Ubuntu Linux|17.10|Debian
2|CentOS|7.x|RedHat

E aqui não tem pretty pra ajudar… 🙂

Definindo quais chaves exibir

Digamos que apenas algumas chaves (campos) precisem ser exibidas? Por exemplo, somente “name” e “version”:

> db.oses.find({},{ _id:0, name:1, version:1 })
{ "name" : "Ubuntu Linux", "version" : "17.10" }
{ "name" : "CentOS", "version" : "7.x" }

Basta referenciar quais chaves devem ser mostradas e como “kind” não foi listada, não constará da lista. A exceção da regra é a chave “_id” que sempre será exibida a não ser que seja explicitamente ocultada da listagem.

E a sintaxe correspondente em SQL é a seguinte:

sqlite> SELECT name,version FROM oses;
Ubuntu Linux|17.10
CentOS|7.x

Ordenando a lista de documentos

Para ordenar os documentos direcione a saída do método find() para um segundo método, o sort():

> db.oses.find({},{ _id:0, name:1, version:1 }).sort({ name: 1 })
{ "name" : "CentOS", "version" : "7.x" }
{ "name" : "Ubuntu Linux", "version" : "17.10" }

Neste caso a saída será ordenada pela chave “name” e em ordem crescente “1”. O objeto usado como parâmetro no sort() aceita os valores “1” para indicar ordem crescente e “-1” para decrescente.

Em SQL a sintaxe equivalente é:

sqlite> SELECT name,version FROM oses ORDER BY name ASC;
CentOS|7.x
Ubuntu Linux|17.10

Definindo o critério de pesquisa

Aqui está o motivo para ter importado a base de dados dos meteoritos, ter algo substancial e brincar com dados reais! Como, por exemplo, descobrir quais foram os meteoritos com massa acima de 10kg cuja queda foi registrada na superfície terrestre:

> db.meteorites.find({mass:{$gte:10000}},{name:1,mass:1}).sort(
... {mass:-1}).limit(10)
{ "_id" : "11890", "name" : "Hoba", "mass" : 60000000 }
{ "_id" : "5262", "name" : "Cape York", "mass" : 58200000 }
{ "_id" : "5247", "name" : "Campo del Cielo", "mass" : 50000000 }
{ "_id" : "5257", "name" : "Canyon Diablo", "mass" : 30000000 }
{ "_id" : "2335", "name" : "Armanty", "mass" : 28000000 }
{ "_id" : "10912", "name" : "Gibeon", "mass" : 26000000 }
{ "_id" : "5363", "name" : "Chupaderos", "mass" : 24300000 }
{ "_id" : "16852", "name" : "Mundrabilla", "mass" : 24000000 }
{ "_id" : "23593", "name" : "Sikhote-Alin", "mass" : 23000000 }
{ "_id" : "4919", "name" : "Bacubirito", "mass" : 22000000 }

O conteúdo do objeto utilizado para a consulta contém a chave “mass” (a massa do meteorito em gramas) e dentro um operador $gte seguido do valor da condição. Existem diversos operadores para consulta disponíveis mas, por enquanto, uma lista curta dos operadores de comparação:

  • $eq — igual a;
  • $gt — maior que;
  • $gte — maior ou igual a;
  • $lt — menor que;
  • $lte — menor ou igual a e
  • $neq — não igual a (diferente).

Em SQL esta mesma consulta seria:

sqlite> SELECT _id,name,mass FROM meteorites
   ...> WHERE mass>=10000 ORDER BY mass DESC LIMIT 10;
11890|Hoba|60000000.0
5262|Cape York|58200000.0
5247|Campo del Cielo|50000000.0
5257|Canyon Diablo|30000000.0
2335|Armanty|28000000.0
10912|Gibeon|26000000.0
5363|Chupaderos|24300000.0
16852|Mundrabilla|24000000.0
23593|Sikhote-Alin|23000000.0
4919|Bacubirito|22000000.0

Contando o número de documentos

E para terminar esta parte, o método count() que totaliza a quantidade de documentos resultantes da consulta:

> db.meteorites.find({mass:{$gte:10000}},{name:1,mass:1}).sort(
... {mass:-1}).count()
1388

E, para não perder o hábito, seu análogo em SQL:

sqlite> SELECT count(_id) FROM meteorites
   ...> WHERE mass>=10000 ORDER BY mass DESC LIMIT 10;
1388

Fim desta parte

Bem, a ideia era ter coberto todas as operações do CRUD nesta parte mas como inserção e recuperação juntas já resultou em um texto longo e como atualização possui algumas peculiaridades, ela e a remoção ficarão para a próxima parte.

Até!

Anúncios

Um comentário sobre “Usando o MongoDB – parte 2

  1. Pingback: Usando o MongoDB – parte 3 | giovannireisnunes

Os comentários estão desativados.