Descomplicando o MongoDB: Guia prático para iniciantes

Descomplicando o MongoDB: Guia prático para iniciantes

Trabalhar com o Mongodb é difícil?
Parece um bicho de sete cabeças?

Este post vai descomplicar o MongoDB, mostrando que ele é mais fácil do que você imagina.

Entendendo o conceito

Basicamente o mongodb trabalha com json.
O mais difícil é entender isso, deixar de lado o sql padrão que você domina a muito tempo, e usar um povo padrão: json.

Para facilitar o uso, seguem algumas dicas de ouro:

Dica 1: Tome muito cuidado com as vírgulas. A grande maioria de erros em queries no mongodb é por falta de vírgula.
Dica 2: Sempre tente identar o seu código, ou use um formatter de json online. Assim facilita a identificação de eventuais problemas.

Um dataset para o nosso laboratório

Para tornar os nossos exemplos mais concretos e mais fáceis de serem reproduzidos, vamos utilizar um dataset do próprio mongodb.
Este dataset está disponível no atlas, assim como outros, e servem para um propósito educacional. Clique aqui para baixar.

Aqui já vai a primeira lição:

Existem duas formas de fazer o backup: Em formato de diretório ou em formato de arquivo único (Archive).
Como o backup baixado está no formato archive, para importar os dados, basta executar o seguinte comando, dentro do diretório onde você baixou o dataset:

mongorestore --archive=sample_mflix.archive

Obs: Lembre-se que se o seu mongodb estiver com autenticação ativa, você deve adicionar o usuário e senha no comando de restore.

A saída será algo semelhante a:

2023-02-15T02:49:13.818+0000 66359 document(s) restored successfully. 0 document(s) failed to restore.

Conecte-se no mongodb via mongo-shell e perceba que o banco foi restaurado, com o mesmo nome: sample_mflix

test> show dbs
admin 40.00 KiB
config 12.00 KiB
local 40.00 KiB
sample_mflix 18.14 MiB

test> use sample_mflix
switched to db sample_mflix

sample_mflix> show collections
comments
movies
sessions
theaters
users

Executando queries

Uma vez que os dados foram carregados, vamos continuar nosso laboratório de MongoDB.

Uma query simples

Para buscar os filmes que sejam do ano 1917, executando um código SQL caso fosse um banco relacional, seria algo como o seguinte comando:

SELECT * FROM MOVIES WHERE year = 1917;

Já no mongodb, você deve sempre passar um documento que terá o filtro desejado.
O comando para buscar registros no mongo é o find.
Portanto, a query seria algo semelhante a:

sample_mflix> db.movies.find({year: 1917});

O resultado será um array de jsons, com os 3 registros existentes na base.

E se eu quiser escolher os campos retornados ?

Da mesma forma que os bancos relacionais, retornar todos os campos numa query não é algo recomendável, pois terá um maior consumo de recursos do banco.

No SQL, para selecionar os campos title, released e type, a query seria algo semelhante a:

SELECT title, released, type FROM MOVIES WHERE year = 1917;

Já no mongodb, este operador é chamado de projection. Ele pode ser usado dentro de um find comum, ou dentro de um aggregation pipeline. Dentro de um find, ele deve ser utilizado como o próximo documento após o documento de busca. A sintaxe é:

sample_mflix> db.movies.find({year: 1917},{title:1, released:1, type:1});
[
{
_id: ObjectId("573a1390f29313caabcd60e4"),
title: 'The Immigrant',
released: ISODate("1917-06-17T00:00:00.000Z"),
type: 'movie'
},
{
_id: ObjectId("573a1390f29313caabcd6223"),
title: 'The Poor Little Rich Girl',
released: ISODate("1917-03-05T00:00:00.000Z"),
type: 'movie'
},
{
_id: ObjectId("573a1390f29313caabcd6377"),
title: 'Wild and Woolly',
released: ISODate("1917-06-24T00:00:00.000Z"),
type: 'movie'
}
]

 

Como vocês puderam perceber, o _id foi retornado mesmo não sendo solicitado na query.
Este é o comportamento padrão do mongodb, ele sempre irá retornar o _id, em qualquer query.
Para retirá-lo, você deve retirá-lo manualmente, utilizando a opção 0, dentro do projection:

sample_mflix> db.movies.find({year: 1917},{title:1, released:1, type:1,_id:0});
[
{
title: 'The Immigrant',
released: ISODate("1917-06-17T00:00:00.000Z"),
type: 'movie'
},
{
title: 'The Poor Little Rich Girl',
released: ISODate("1917-03-05T00:00:00.000Z"),
type: 'movie'
},
{
title: 'Wild and Woolly',
released: ISODate("1917-06-24T00:00:00.000Z"),
type: 'movie'
}
]

Alias para os campos?

Para adicionar um alias no SQL, basta adicionar a palavra ‘AS’ mais a palavra que você deseja para o campo. A sintaxe seria semelhante a:

SELECT
title AS titulo,
released AS data_lancamento,
type AS tipo
FROM
MOVIES
WHERE
year = 1917;

Para adicionar um alias no mongodb, basta alterar o projection, da seguinte forma: coloque o nome desejado para o campo, e depois o campo da collection precedido por um cifrão ($). Esta mesma abordagem pode ser utilizada também dentro de um aggregation pipeline.

sample_mflix> db.movies.find({year: 1917},{titulo: '$title', data_lancamento: '$released', tipo:'$type', id: '$_id', _id: 0});
[
{
titulo: 'The Immigrant',
data_lancamento: ISODate("1917-06-17T00:00:00.000Z"),
tipo: 'movie',
id: ObjectId("573a1390f29313caabcd60e4")
},
{
titulo: 'The Poor Little Rich Girl',
data_lancamento: ISODate("1917-03-05T00:00:00.000Z"),
tipo: 'movie',
id: ObjectId("573a1390f29313caabcd6223")
},
{
titulo: 'Wild and Woolly',
data_lancamento: ISODate("1917-06-24T00:00:00.000Z"),
tipo: 'movie',
id: ObjectId("573a1390f29313caabcd6377")
}
]

Incrementando a sua query

E como podemos incrementar a nossa query? Como usar o operador maior, menor ou diferente?
A lista completa de operadores no mongodb está disponível no link:
De modo geral, para utilizar um operador, basta utilizar o cifrão ($) mais o nome do operador. Normalmente os operadores são sucedidos de um novo documento ou um array.

Segue uma query mais elaboradora para análise:

db.movies.find({
title: /harry potter/i
,genres: { $in: ['Adventure'] }
,released: { $gte: ISODate("2006-11-19T00:00:00.000Z"), $lte: ISODate("2013-11-19T00:00:00.000Z")}
,$or: [
{ year: 2007 },
{ year: 2009 }
]
},{_id:0,title:1,genres:1,released:1,year:1})

Para o título, fizemos um operador // que significa uma expressão regular. O i posterior significa case insensitive. Assim, estamos pesquisando por todos os filmes que contenham as palavras ‘harry potter’, nesta ordem, sendo maiúsculas ou minúsculas.

Posteriormente pesquisamos por todos os filmes, que satisfaçam o filtro anterior de título, e também que tenham o gênero Aventura (Adventure). Note que o filme pode ter outros gêneros, mas necessariamente precisa possuir o gênero aventura.

Já para a data de lançamento, pesquisamos por todos os filmes que lançaram depois de 19/11/2006 e antes de 19/11/2013. Note que, caso o campo no mongodb seja de data, devemos usar a função ISODate(). Caso a expressão esteja apenas entre aspas, o mongodb irá procurar por valores STRING, e não retornará nada.

Por fim, vimos que com o mongodb é possível combinar vários filtros e eles funcionam com um grande operador AND. Caso você queira utilizar o operador OR, o mesmo deve ser explícito, como no caso do ano, que procuramos apenas filmes de 2007 ou 2009.

Obs: O Operador OR não é muito seletivo, e por muitas vezes é feito um Collection Scan nas buscas. Evite-o sempre que possível.

Query dentro de um subdocumento?

Vimos alguns exemplos com campos simples, e arrays. Mas se o dado procurado estiver dentro de um subdocumento, como fazer?

Basta adicionar o nome com a seguinte sintaxe: ‘campo.subcampo’. Neste caso, as aspas são obrigatórias, e podem ser usadas aspas simples ou duplas.

db.movies.find({
'awards.wins': { $gte: 200 }
},{_id:0,title:1,'awards.wins':1, 'vitorias': '$awards.wins' })
[
{ title: 'Gravity', awards: { wins: 231 }, vitorias: 231 },
{ title: 'Gravity', awards: { wins: 231 }, vitorias: 231 },
{ title: '12 Years a Slave', awards: { wins: 267 }, vitorias: 267 },
{
title: 'Birdman: Or (The Unexpected Virtue of Ignorance)',
awards: { wins: 210 },
vitorias: 210
}
]

A mesma coisa vale para o projection. Perceba que quando você utiliza um projection de campo + subcampo, o mongo retornará no formato original, com o subdocumento. Se você precisa apenas do valor, defina um alias para o campo, como por exemplo, o alias ‘vitorias’.

O aggregate pipeline

Certo, e como podemos utilizar group by e demais funções no mongodb? Bom, há duas formas: A primeira é fazer manualmente com o forEach, e a segunda seria com o aggregate Pipeline.

O Aggregate pipeline funciona por estágios sequenciais. Cada estágio vai realizar a ação desejada e a saída do primeiro estágio vai ser a entrada do segundo, e assim por diante.

Dica de ouro:
Já que o mongodb executa por estágios, escreva o código também por estágios, assim você garante que o mesmo irá funcionar, e facilita eventuais correções.

Para fazer um aggregate, basta utilizar o comando aggregate, e o mesmo deve conter um array com os estágios. Para começar, vamos fazer uma busca simples pelo diretor Quentin Tarantino. Lembrando que dentro do aggregate, o estágio de busca é o $match.

db.movies.aggregate([
{ "$match": { directors: { $in: ['Quentin Tarantino'] }} }
])

Avançando mais um pouquinho no aggregate, podemos adicionar um group by para retornar quantos filmes foram lançados por ano, e uma média das notas no imdb, dos filmes do Quentin Tarantino.

db.movies.aggregate([
{ "$match": { directors: { $in: ['Quentin Tarantino'] }} },
{ "$group": { _id: "$year", 'filmesPorAno': { $sum: 1 }, 'media_imdb': { $avg: '$imdb.rating'} } },
{ "$sort": { "_id": 1} }
])

Provando que o aggregation pipeline funciona como estágios, após o $group e $sort, podemos ainda fazer um novo filtro, para retornar todos os anos que tiveram mais de um filme, como exemplo:

db.movies.aggregate([
{ "$match": { directors: { $in: ['Quentin Tarantino'] }} },
{ "$group": { _id: "$year", 'filmesPorAno': { $sum: 1 }, 'media_imdb': { $avg: '$imdb.rating'} } },
{ "$sort": { "_id": 1} },
{ "$match": { "filmesPorAno": { $gt: 1} }}
])

E como funciona o forEach?

O forEach funciona de forma semelhante ao aggregate. A diferença é que com ele você pode utilizar o código javaScript da maneira que achar melhor. É muito utilizado quando você tem uma collection muito grande, e deseja alterar ou excluir determinados registros.
Um exemplo seria para deleção de, no máximo, 20 filmes que sejam maiores do que o ano 2000, ordenados pela data de lançamento crescendo.

Bom, para deleção no mongodb, você pode usar o deleteMany, ou o deleteOne. O problema é que, apesar de ser possível fazer filtros, não é possível fazer limites na deleção.

Com o foreach, podemos fazer em etapas também, primeiro vamos apenas listar os filmes que serão removidos.

db.movies.find({
year: { $gt: 2000}
}).sort({
released:1
}).limit(20).forEach(
function(filme) {
print("Nome do filme: " + filme.title);
}
)
Nome do filme: To Kill a King
Nome do filme: Jailbait
Nome do filme: Flight from Death: The Quest for Immortality
Nome do filme: War Photographer
Nome do filme: Dischord
Nome do filme: Dating Games People Play
Nome do filme: Julie and Jack
Nome do filme: Pithamagan
Nome do filme: In Memory of My Father
Nome do filme: What Matters Most
Nome do filme: The Mudge Boy
Nome do filme: A2
Nome do filme: Dischord
Nome do filme: Ever Since the World Ended
Nome do filme: Romance & Cigarettes
Nome do filme: The Fallen
Nome do filme: Blackballed: The Bobby Dukes Story
Nome do filme: The Woodsman
Nome do filme: Prevrashchenie
Nome do filme: Gefèngnisbilder

Vamos fazer um count, para conferir quantos filmes estão cadastrados hoje:

sample_mflix> db.movies.find().count()
23530

E para deleção, podemos executar uma nova função, dentro do forEach, como exemplo:

db.movies.find({
year: { $gt: 2000}
}).sort({
released:1
}).limit(20).forEach(
function(filme) {
db.movies.deleteOne({_id: filme._id})
}
)

Perceba que apesar do deleteOne deletar somente um registro, a função foi chamada 20 vezes, por isso deletamos 20 registros. Utilizem sempre o _id no filtro da deleção, primeiro porque ele sempre será único pelo índice de unicidade, e sempre será performático, também por conta do índice.

Conferindo novamente, podemos ver que exatamente 20 registros foram deletados:

sample_mflix> db.movies.find().count()
23510

Viu como é fácil?

Trabalhar com o mongoDB é mais fácil do que você imaginava.
Estes são alguns exemplos de queries que você pode fazer no dia-a-dia.
Talvez no mundo real você esbarre em alguns casos mais complexos, mas tendo uma boa base de queries, você vai ser capaz de elaborar qualquer uma!

A 4linux tem um ótimo curso de mongoDB, procure um de nossos consultores!

 

Líder em Treinamento e serviços de Consultoria, Suporte e Implantação para o mundo open source. Conheça nossas soluções:

CURSOSCONSULTORIA

Anterior Entenda o que é e como funciona uma Máquina Virtual
Próxima Entenda a Importância e Utilização das Variáveis de Ambiente em Linux

About author

Você pode gostar também

Banco de Dados

4Linux: Líder em Implementação de Banco de Dados PostgreSQL

A 4Linux implementou um dos maiores cases mundiais de banco de dados PostgreSQL  que chegou até mesmo a ser palestrado no maior evento mundial de PostgreSQL, o PGCON. O case do Datasus também

Banco de Dados

Organize seus objetos de banco de dados com schemas PostgreSQL no Django

Que tal se você pudesse organizar seus objetos de bancos de dados (suas tabelas, views, functions, procedures etc.) em namespaces de acordo com suas respectivas funções no sistema? Neste artigo

Banco de Dados

Otimização de Consultas PostgreSQL com Postgres Explain Visualizer v2

No mundo dos bancos de dados, a otimização de consultas desempenha um papel vital na garantia de um desempenho eficiente e ágil. Com o crescente volume de dados e as