Este tutorial apresenta o banco de dados MongoDB. Você aprenderá como instalar o software, manipular dados e aplicar técnicas de design de dados em seus próprios aplicativos.

Todos os exemplos foram desenvolvidos usando o MongoDB 5, mas a maioria funcionará em versões anteriores ou posteriores. O código pode ser inserido diretamente em um aplicativo cliente ou no shell do MongoDB (mongo ou mongosh) para consultar e atualizar o banco de dados.

O que é MongoDB?

O MongoDB é um banco de dados de código aberto NoSQL. NoSQL significa que o banco de dados não utiliza tabelas relacionais como um banco de dados SQL tradicional.

Existem diversos tipos de bancos de dados NoSQL, mas o MongoDB armazena dados em objetos semelhantes a JavaScript chamados de documentos, cujo conteúdo se parece com isso:

{
  _id: "123",
  name: "Craig"
}


Embora MongoDB tenha se tornado sinônimo do framework baseado em JavaScript Node.js, os drivers oficiais do MongoDB estão disponíveis para a maioria dos frameworks, linguagens e tempos de execução, incluindo Node.js, PHP e Python. Você também pode optar por bibliotecas como Mongoose que oferecem um nível mais elevado de abstração ou recursos de mapeamento relacional de objetos (ORM).

Ao contrário das tabelas SQL, não existem limites estruturais sobre o que você pode armazenar no MongoDB. Os esquemas de dados não são impostos: você pode armazenar o que quiser onde quiser. Isso torna o MongoDB ideal para estruturas de dados mais orgânicas – ou confusas.

Considere um livro de endereços de contatos. É comum que os indivíduos tenham vários números de telefone. Você poderia definir três campos de telefone na tabela SQL, isso pode ser insuficiente para alguns contatos e excessivo para outros. No final, você precisaria de uma tabela separada de telefone, o que adiciona mais complexidade.

No MongoDB, esses números de telefones poderiam ser definidos como um conjunto ilimitado de objetos no mesmo documento:

{
  _id: "123",
  name: "Craig",
  telephone: [
    { home: "0123456789" },
    { work: "9876543210" },
    { cell: "3141592654" }
  ]
}

Observe que o MongoDB usa uma notação de objeto JavaScript semelhante para atualizações e consultas de dados, o que pode representar alguns desafios se você estiver acostumado com o SQL.

Elementos do MongoDB

Antes de continuarmos, daremos uma olhada no que faz o MongoDB funcionar. Usaremos este vocabulário ao longo deste artigo.

  • Documento: Um objeto individual em um banco de dados, análogo a um registro ou linha em uma tabela de banco de dados SQL.
  • Campo: Um único item de dados dentro de um documento, como um nome ou número de telefone, análogo a um campo ou coluna de tabela SQL.
  • Coleção: Um conjunto de documentos semelhantes, análogo a uma tabela SQL. Embora seja possível colocar todos os documentos em uma única coleção, geralmente é mais prático agrupá-los em tipos específicos. Em um livro de endereços de contatos, você poderia ter uma “person collection” e outra para empresas.
  • Banco de dados: Uma coleção de dados relacionados, idênticos em significado a um banco de dados SQL.
  • Schema: Um schema define estruturas de dados. Em bancos de dados SQL você deve definir definições de tabelas com campos e tipos associados antes de poder armazenar dados. Isso não é necessário no MongoDB, embora seja possível criar um schema que valide documentos antes que eles possam ser adicionados a uma coleção.
  • Índice: Uma estrutura de dados usada para melhorar o desempenho de consultas, com o mesmo significado que índices SQL.
  • Chave primária: Um identificador único para cada documento. MongoDB automaticamente adiciona um campo _id único e indexado a cada documento de uma coleção.
  • Desnormalização: Em bancos de dados SQL, “normalização” é uma técnica usada para organizar e eliminar dados duplicados. No MongoDB, embora não seja recomendado, a “desnormalização” pode ser usada em determinadas situações para melhorar o desempenho da consulta. Isso envolve a repetição ativa de dados, permitindo que um único documento contenha todas as informações necessárias.
  • Join: SQL fornece um operador JOIN para que os dados possam ser recuperados de múltiplas tabelas normalizadas em uma única consulta. A JOIN não era possível no MongoDB até a versão 3.6 e as limitações permanecem. Esta é outra razão pela qual os dados devem ser desnormalizados em documentos autocontidos.
  • Transações: Quando uma atualização muda dois ou mais valores em um único documento, o MongoDB garante que todos eles tenham sucesso ou que todos falhem. As atualizações em dois ou mais documentos devem ser embrulhadas em uma transação. O MongoDB tem suportado transações desde a versão 4.0, mas uma réplica multi-servidor ou um cluster fragmentado é necessário. As instalações de exemplo abaixo usam um único servidor para que as transações não sejam possíveis.

Como instalar o MongoDB

Você tem três opções para usar o MongoDB em sua máquina local. Vamos guiá-lo através de cada uma delas.

1. Use o Docker (Recomendado)

Docker é uma ferramenta de gerenciamento de software que pode instalar, configurar e executar o MongoDB ou qualquer outro aplicativo em minutos.

Instale Docker e Docker Compose e, em seguida, crie uma pasta de projeto com um único arquivo chamado docker-compose.yml contendo o seguinte conteúdo (note que as indentações são essenciais):

version: '3'

services:

  mongodb:
    image: mongo:5
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root
      - MONGO_INITDB_ROOT_PASSWORD=pass
      - MONGO_INITDB_DATABASE=mongodemo
    container_name: mongodb
    volumes:
      - dbdata:/data/db
    ports:
      - "27017:27017"

  adminer:
    image: dehy/adminer
    container_name: adminer
    depends_on:
      - mongodb
    ports:
      - "8080:80"

volumes:
  dbdata:

Acesse a pasta a partir da linha de comando e execute:

docker-compose up

A versão mais recente do MongoDB 5 será baixada e iniciada. Isso levará alguns minutos na primeira vez, mas as execuções subsequentes são consideravelmente mais rápidas.

Note que:

  • Uma conta de administrador do MongoDB é definida com o ID “root” e senha “pass”.
  • Os dados são salvos entre as reinicializações em um volume de Docker chamado dbdata.
  • O cliente do banco de dados do Adminer também é fornecido.

Você pode usar qualquer cliente de banco de dados MongoDB para se conectar ao localhost: 27017 usando o ID “root” e senha “pass”. Alternativamente, você pode acessar Adminer em http://localhost:8080/ e fazer o login com as seguintes credenciais:

  • Sistema: MongoDB (alfa)
  • Servidor: host.docker.internal
  • Nome de usuário: root
  • Senha: pass
Adminer login
Adminer login

Adminer permite que você inspecione coleções e documentos. Esteja ciente, entretanto, que as coleções são referidas como “tabelas”:

Adminer collection view
Visualização da coleção Adminer.

Para executar comandos, você pode usar o ambiente MongoDB Shell (mongosh) ou a linha de comando REPL (Read Eval Print Loop) do legado mongo.

Acesse o shell bash do contêiner Docker MongoDB:

docker exec -it mongodb bash

Em seguida, inicie o shell do MongoDB com o ID e senha:

mongosh -u root -p pass

(O comando legado mongo pode ser usado se você preferir)

Você pode então emitir comandos MongoDB como os seguintes:

  • show dbs; – Mostrar todos os bancos de dados
  • use mongodemo; – Use um banco de dados específico
  • show collections; – Liste as coleções em um banco de dados
  • db.person.find(); – Liste todos os documentos de uma coleção
  • exit; – Sair/fechar o shell

Desative o MongoDB executando o seguinte comando no diretório do projeto:

docker-compose down

2. Use um provedor de nuvens (sem instalação)

Você pode usar uma instância hospedada do MongoDB, então não é necessário instalar nada localmente. Uma conexão com a internet é essencial e a velocidade de resposta dependerá do provedor de hospedagem e da sua largura de banda. A maioria dos serviços irá cobrar uma taxa mensal e/ou de uso em Megabytes.

O provedor de hospedagem normalmente fornecerá detalhes para que você possa administrar o banco de dados remotamente usando o software cliente MongoDB.

3. Instale MongoDB localmente

MongoDB pode ser instalado e configurado em Linux, Windows, ou Mac OS. Duas edições estão disponíveis:

  1. Uma edição comercial Enterprise
  2. Uma edição de código aberto Community (usada neste tutorial)

A página de instalação do MongoDB fornece instruções para vários sistemas operacionais. Em geral:

Certifique-se de seguir as instruções cuidadosamente para que sua instalação seja bem-sucedida.

Como acessar o seu banco de dados MongoDB

Agora que o seu banco de dados MongoDB está instalado, é hora de aprender como gerenciá-lo. Discutiremos o que você precisará fazer para acessar e trabalhar com o seu banco de dados.

1. Instale um cliente MongoDB

Um aplicativo cliente MongoDB é necessário para administrar bancos de dados. Caso você esteja usando uma nuvem ou instalação local, recomendamos que você instale a linha de comando mongosh MongoDB Shell.

Adminer é um cliente de banco de dados baseado na web que suporta o MongoDB, embora atualmente ele esteja limitado a inspecionar coleções. Adminer pode ser baixado como um único script PHP, mas já está configurado se você usar a instalação do Docker ou tiver o DevKinsta instalado.

Um aplicativo cliente GUI fornece uma melhor interface para atualizar e inspecionar de dados. Existem várias opções disponíveis, incluindo o MongoDB Compass, que é gratuito e multi-plataforma:

MongoDB Compass
MongoDB Compass

O Studio 3T, outro concorrente da GUI, fornece um aplicativo comercial que concede funcionalidade limitada gratuitamente:

Studio 3T
Studio 3T

Você pode acessar seu banco de dados MongoDB com uma dessas ferramentas usando qualquer uma das seguintes opções:

  1. O nome da rede da máquina, URL, ou endereço IP (localhost para uma instalação local).
  2. A porta MongoDB (27017 por padrão).
  3. Um ID de usuário e uma senha. Um usuário root é normalmente definido durante a instalação.

2. Defina e salve as credenciais de acesso ao banco de dados

O administrador root tem acesso ilimitado a todos os bancos de dados. Em geral, você deve usar um usuário personalizado com privilégios específicos para limitar o acesso e aumentar a segurança.

Por exemplo, o seguinte comando cria um usuário chamado myuser com a senha mypass que tem acesso de leitura e escrita ao banco de dados mydb:

use mydb;

db.createUser({
  user: "myuser",
  pwd: "mypass",
  roles: [
    { role: "readWrite", db: "mydb" }
  ]
});

Como inserir novos documentos no MongoDB

Não há necessidade de definir um banco de dados ou uma coleção antes de inserir seu primeiro documento. Usando qualquer cliente MongoDB, simplesmente mude para um banco de dados chamado mongodemo:

use mongodemo;

Então insira um único documento em uma nova “person collection”:

db.person.insertOne(
  {
    name: 'Abdul',
    company: 'Alpha Inc',
    telephone: [
      { home: '0123456789' },
      { work: '9876543210' }
    ]
  }
);

Veja o documento executando uma consulta para retornar todos os resultados da coleta da pessoa:

db.person.find({});

O resultado será algo parecido com isto:

{
  "_id" : ObjectId("62442429854636a03f6b8534"),
  name: 'Abdul',
  company: 'Alpha Inc',
  telephone: [
    { home: '0123456789' },
    { work: '9876543210' }
  ]
}

Como inserir múltiplos documentos

Você pode inserir múltiplos documentos em uma coleção passando um array para inserirMany(). O código a seguir cria documentos pessoais adicionais e uma nova coleção da empresa:

db.person.insertMany([
  {
    name: 'Brian',
    company: 'Beta Inc'
  },
  {
    name: 'Claire',
    company: 'Gamma Inc',
    telephone: [
      { cell: '3141592654' }
    ]
  },
  {
    name: 'Dawn',
    company: 'Alpha Inc'
  },
  {
    name: 'Esther',
    company: 'Beta Inc',
    telephone: [
      { home: '001122334455' }
    ]
  },
  {
    name: 'George',
    company: 'Gamma Inc'
  },
  {
    name: 'Henry',
    company: 'Alpha Inc',
    telephone: [
      { work: '012301230123' },
      { cell: '161803398875' }
    ]
  },
]);

db.company.insertMany([
  {
    name: 'Alpha Inc',
    base: 'US'
  },
  {
    name: 'Beta Inc',
    base: 'US'
  },
  {
    name: 'Gamma Inc',
    base: 'GB'
  },
]);

De onde vem o _id?

MongoDB atribui automaticamente um _id a cada documento de uma coleção. Este é um ObjectID – um valor BSON (Binary Javascript Object Notation) contendo:

  • Período Unix epoch em segundos quando criado (4 bytes)
  • ID de máquina/processo de 5 bytes
  • Um contador de 3 bytes que começa com um valor aleatório

Esta é a chave primária do documento. O valor hexadecimal de 24 caracteres é garantido como único em todos os documentos do banco de dados, e não pode ser alterado uma vez inserido.

MongoDB também fornece uma função getTimeStamp() para que você possa obter a data/hora de criação do documento sem ter que definir um valor explicitamente. Alternativamente, você pode definir seu próprio valor exclusivo _id quando um documento é criado.

Desnormalização de dados

Os registros inseridos acima definem a empresa de cada usuário para uma cadeia como a “Alpha Inc”. Isso não é recomendado em bancos de dados SQL normalizados:

  • É fácil cometer um erro: um usuário é designado para “Alpha Inc” enquanto outro é “Alpha Inc.” (caráter de período adicional). Eles são tratados como empresas diferentes.
  • Atualizar o nome de uma empresa pode significar a atualização de muitos registros.

A solução SQL é criar uma tabela da empresa e associar uma empresa a uma pessoa usando sua chave primária (provavelmente um número inteiro). A chave permaneceria a mesma independentemente da alteração do nome da empresa e o banco de dados pode aplicar regras para garantir a integridade dos dados.

A desnormalização é recomendada para MongoDB. Você deve repetir ativamente os dados e um único documento pode conter todas as informações que ele requer. Isso tem várias vantagens:

  • Os documentos são autocontidos e mais fáceis de ler — não há necessidade de fazer referência a outras coleções.
  • O desempenho de escrita pode ser mais rápido do que um banco de dados SQL porque menos regras de integridade de dados são aplicadas.
  • O compartilhamento — ou a distribuição de dados entre várias máquinas — torna-se mais fácil porque não é necessário fazer referência a dados em outras coleções.

Consultas simples no MongoDB

Você pode listar todos os documentos de uma coleção, como uma pessoa, usando uma consulta find():

db.person.find({})

O count() method retorna o número de documentos (em nosso caso, esse número será 7):

db.person.find({}).count();

Um método sort() retorna os documentos em qualquer ordem que você preferir, como, por exemplo, por nome em ordem alfabética inversa:

db.person.find({}).sort({ name: -1 });

Você também pode limitar o número de documentos retornados, por exemplo, encontrar os três primeiros nomes:

db.person.find({}).sort({ name: 1 }).limit(2);

Você pode procurar por registros específicos definindo uma consulta com um ou mais campos, por exemplo, localizar todos os documentos pessoais onde o nome está definido como “Claire”:

db.person.find({ name: 'Claire' });

Operadores lógicos como $and, $or, $not, $gt (maior que), $lt (menor que), e $ne (não igual), também são suportados, por exemplo, localizar todos os documentos pessoais onde a empresa é “Alpha Inc” ou “Beta Inc”:

db.person.find({
  $or: [
    { company: 'Alpha Inc' },
    { company: 'Beta Inc' }
  ]
});

No banco de dados do exemplo, o mesmo resultado poderia ser alcançado com $nin (não em) para extrair todos os documentos onde a empresa não é “Gamma Inc”:

db.person.find({
  company: { $nin: ['Gamma Inc'] }
});

Ao utilizar o método find() no MongoDB, é possível definir uma projeção através de um segundo objeto de valor, que especifica quais campos devem ser retornados. No exemplo dado, apenas o campo “name” é retornado (lembrando que o campo “_id” é sempre retornado a menos que seja explicitamente desativado):

db.person.find(
  { name:'Claire' },
  { _id:0, name:1 }
);

O resultado:

{
  "name" : "Claire"
}

A consulta $elemMatch permite que você encontre itens em uma array, como todos os documentos onde a array telefônica tem um item de trabalho. O mesmo $elemMatch pode ser usado na projeção para mostrar apenas o número do trabalho:

db.person.find(
  {
    telephone: { $elemMatch: { work: { $exists: true }} }
  },
  {
    _id: 0,
    name:1,
    telephone: { $elemMatch: { work: { $exists: true }}}
  }
);

O resultado:

{
  "name" : "Abdul",
  "telephone" : [
    { "work" : "9876543210" }
  ]
},
{
  "name" : "Henry",
  "telephone" : [
    { "work" : "012301230123" }
  ]
}

Usando cursores no MongoDB

A maioria dos drivers de banco de dados permite que os resultados de uma consulta sejam retornados como uma array ou estrutura de dados similar. Entretanto, se esse conjunto contiver milhares de documentos, isso pode levar a problemas de memória.

Como a maioria dos bancos de dados SQL, o MongoDB suporta o conceito de cursores. Os cursores permitem que um aplicativo leia os resultados da consulta um de cada vez antes de prosseguir para o próximo item ou abandonar a busca.

Cursores também podem ser usados a partir de uma shell MongoDB:

let myCursor = db.person.find( {} );

while ( myCursor.hasNext() ) {
  print(tojson( myCursor.next() ));
}

Como criar índices no MongoDB

A “person collection” atualmente possui sete documentos, portanto qualquer consulta não será computacionalmente cara. Entretanto, imagine que você tenha um milhão de contatos com um nome e um endereço de e-mail. Os contatos podem ser ordenados por nome, mas os endereços de e-mail estarão em uma ordem aparentemente aleatória.

Se você precisar procurar um contato pelo e-mail dele, o banco de dados teria que procurar até um milhão de itens antes de encontrar uma correspondência. Adicionar um índice no campo de e-mail cria uma “tabela” de pesquisa onde os e-mails são armazenados em ordem alfabética. O banco de dados agora pode usar algoritmos de busca mais eficientes para localizar a pessoa correta.

Os índices se tornam essenciais à medida que o número de documentos aumenta. Em geral, você deve aplicar um índice a qualquer campo que possa ser referenciado em uma consulta. Você pode aplicar índices a todos os campos, mas esteja ciente de que isso tornaria as atualizações de dados mais lentas e aumentaria o espaço em disco necessário, pois a reindexação se torna necessária.

O MongoDB oferece uma variedade de tipos de índices.

Índices de campo único

A maioria dos índices no MongoDB são aplicados a campos individuais, por exemplo, indexar o campo “name” em ordem alfabética ascendente:

db.person.createIndex({ name: 1 });

Usar -1 inverte a ordem. Isso teria pouco benefício em nosso exemplo aqui, mas pode ser útil se você tiver um campo de data em que eventos mais recentes tenham prioridade.

Três outros índices são úteis no exemplo do banco de dados mongodemo:

db.person.createIndex( { company: 1 } );
db.company.createIndex( { name: 1 } );
db.company.createIndex( { base: 1 } );

Índices compostos em múltiplos campos

Dois ou mais campos podem ser especificados em um índice, por exemplo:

db.person.createIndex( { name: 1, company: 1 } );

Isso pode ser útil quando um campo é regularmente usado em conjunto com outro em consultas de pesquisa.

Índices multikey em array ou elementos de objetos

Os documentos podem ser complexos e muitas vezes é necessário indexar campos mais detalhados na estrutura, como o número de telefone do trabalho:

db.products.createIndex( { 'telephone.work': 1 } );

Índices de wildcard

Um caractere wildcard pode indexar todos os campos em um documento. Isso é geralmente prático em documentos menores e mais simples que podem ser consultados de várias maneiras:

db.company.createIndex( { '$**': 1 } );

Índices de texto completo

Um índice de texto completo (Fulltext Index) permite criar consultas semelhantes às de um mecanismo de pesquisa, que podem examinar o texto em todos os campos de string e ordenar por relevância. É possível limitar o índice de texto completo a campos específicos:

db.person.createIndex( { name: "text", company: "text" } );

… ou criar um índice de texto em todos os campos de string:

db.person.createIndex( { "$**": "text" } );

O operador $text permite que você pesquise este índice, tal como encontrar todos os documentos onde “Gama” é referenciado:

db.person.find({ $text: { $search: 'Gamma' } });

Observe que as pesquisas de texto completo geralmente exigem cinco ou mais caracteres para retornar resultados úteis.

Outros tipos de índice

A MongoDB fornece vários outros tipos de índices especializados:

Como gerenciar os índices MongoDB

Os índices definidos em uma coleção podem ser examinados com:

db.person.getIndexes();

Isso retorna uma série de resultados, como, por exemplo:

[
  {
    "v" : 2.0,
    "key" : { "_id" : 1.0 },
    "name" : "_id_"
  },
  {
    "v" : 2.0,
    "key" : { "company" : 1.0 },
    "name" : "company_1"
  },
  {
    "v" : 2.0,
    "key" : { "name" : 1.0 },
    "name" : "name_1"
  }
]

A “key” define o campo e a ordem, enquanto o “name” é um identificador único para esse índice — como “company_1” para o índice no campo da empresa.

A eficácia de um índice pode ser examinada adicionando um método .explain() a qualquer consulta, por exemplo.

db.person.find({ name:'Claire' }).explain();

Isso retorna um grande conjunto de dados, mas o objeto “winningPlan” mostra o “indexName” usado na consulta:

"winningPlan" : {
  "stage" : "FETCH",
  "inputStage" : {
    "stage" : "IXSCAN",
    "keyPattern" : { "name" : 1.0 },
    "indexName" : "name_1",
  }
}

Se necessário, você pode excluir um índice referenciando seu nome:

db.person.dropIndex( 'name_1' );

ou usando o documento de especificação do índice:

db.person.dropIndex({ name: 1 });

O método dropIndexes() permite que você exclua mais de um índice em um único comando.

Usando schemas de validação de dados MongoDB

Ao contrário do SQL, schemas de definição de dados não são necessários no MongoDB. É possível inserir qualquer dado em qualquer documento em qualquer coleção a qualquer momento.

Isso proporciona uma grande liberdade. No entanto, pode haver momentos em que você queira exigir que as regras sejam seguidas. Por exemplo, não deve ser possível inserir um documento na “person collection” a menos que ele contenha um nome.

Regras de validação podem ser especificadas usando um objeto $jsonSchema que define uma array de itens obrigatórios e as propriedades de cada campo validado. A “person collection” já foi criada, mas ainda é possível definir um schema que especifica que uma string de nome é obrigatória:

db.runCommand({
  collMod: 'person',
  validator: {
    $jsonSchema: {
      required: [ 'name' ],
      properties: {
        name: {
          bsonType: 'string',
          description: 'name string required'
        }
      }
    }
  }
});

Tente inserir um documento pessoal sem um nome:

db.person.insertOne({ company: 'Alpha Inc' });

… e o comando falhará:

{
  "index" : 0.0,
  "code" : 121.0,
  "errmsg" : "Document failed validation",
  "op" : {
      "_id" : ObjectId("624591771658cd08f8290401"),
      "company" : "Alpha Inc"
  }
}

Schemas também podem ser definidos se você criar uma coleção antes que ela seja utilizada. O comando a seguir implementa as mesmas regras mencionadas acima:

db.createCollection('person', {
  validator: {
    $jsonSchema: {
        required: [ 'name' ],
        properties: {
          name: {
          bsonType: 'string',
          description: 'name string required'
          }
      }
    }
  }
});

Este exemplo mais complexo cria uma coleção de usuários que valida que um nome, endereço de e-mail e pelo menos um número de telefone devem ser fornecidos:

db.createCollection('users', {
  validator: {
    $jsonSchema: {
      required: [ 'name', 'email', 'telephone' ],
      properties: {
        name: {
          bsonType: 'string',
          description: 'name string required'
          },
          email: {
        bsonType: 'string',
          pattern: '^.+@.+$',
          description: 'valid email required'
          },
        telephone: {
          bsonType: 'array',
          minItems: 1,
          description: 'at least one telephone number required'
          }
      }
    }
  }
});

Como atualizar documentos existentes no MongoDB

MongoDB oferece vários métodos de atualização, incluindo updateOne(), updateMany(), e replaceOne(). Eles são passados como parâmetros:

  • Um objeto de filtro que localiza documentos a serem atualizados.
  • Um objeto de atualização — ou um array de objetos de atualização — descrevendo os dados a serem alterados.
  • Um objeto de opções opcional. A propriedade mais útil é a upsert, que pode inserir um novo documento se nenhum for encontrado.

O exemplo a seguir atualiza o documento da pessoa onde o nome está definido como “Henry”. Ele remove o número de telefone do trabalho, adiciona um número de telefone residencial e estabelece uma nova data de nascimento:

db.person.updateOne(
  { name: 'Henry' },
  [
    { $unset: [ 'telephone.work' ] },
    { $set: {
      'birthdate': new ISODate('1980-01-01'),
      'telephone': [ { 'home': '789789789' } ]
    } }
  ]
);

Este próximo exemplo atualiza o documento da pessoa onde o nome está definido como “Ian”. Esse nome não existe atualmente, mas a configuração para “true” o cria:

db.person.updateOne(
  { name: 'Ian' },
  { $set: { company: 'Beta Inc' } },
  { upsert: true }
);

Você pode executar comandos de consulta para examinar as atualizações de dados a qualquer momento.

Como excluir documentos no MongoDB

O exemplo de atualização acima usou $unset para remover o número de telefone do trabalho do documento com o nome “Henry”. Para remover um documento inteiro, você pode usar um dos vários métodos de exclusão, incluindo deleteOne(), deleteMany(), e remove() (que pode excluir um ou vários).

O documento recém-criado para Ian pode ser excluído com um filtro apropriado:

db.person.deleteOne({ name: 'Ian' });

Usando operações de agregação no MongoDB

A agregação é poderosa, mas pode ser difícil de compreender. Ela define uma série — ou pipeline — de operações em uma array. Cada etapa desse pipeline realiza uma operação como filtrar, agrupar, calcular ou modificar um conjunto de documentos. Um estágio também pode usar um comportamento do tipo SQL JOIN com uma operação de $lookup. Os documentos resultantes são passados para o próximo estágio do pipeline para processamento adicional, se necessário.

A agregação é melhor ilustrada com um exemplo prático. Criaremos uma consulta passo a passo que retorna o nome, a empresa e o número de telefone do trabalho (se disponível) de qualquer pessoa que trabalhe para uma organização com sede nos Estados Unidos.

A primeira operação tem um valor de $match para filtrar empresas sediadas nos EUA:

db.company.aggregate([
  { $match: { base: 'US' } }
]);

Isso retorna:

{
  "_id" : ObjectId("62442429854636a03f6b853b"),
  "name" : "Alpha Inc",
  "base" : "US"
}
{
  "_id" : ObjectId("62442429854636a03f6b853c"),
  "name" : "Beta Inc",
  "base" : "US"
}

Em seguida, podemos adicionar um novo operador de pipeline $lookup, que corresponde ao nome da empresa (localField) à empresa (foreignField) na “person collection” (from). A saída será anexada como uma array de funcionários a cada documento de empresa:

db.company.aggregate([
  { $match: { base: 'US' } },
  { $lookup: {
      from: 'person',
      localField: 'name',
      foreignField: 'company',
            as: 'employee'
          }
        }
]);

E aqui está o resultado:

{
  "_id" : ObjectId("62442429854636a03f6b853b"),
  "name" : "Alpha Inc",
  "base" : "US",
  "employee" : [
    {
      "_id" : ObjectId("62442429854636a03f6b8534"),
      "name" : "Abdul",
      "company" : "Alpha Inc",
      "telephone" : [
        { "home" : "0123456789" },
        { "work" : "9876543210" }
      ]
    },
    {
      "_id" : ObjectId("62442429854636a03f6b8537"),
      "name" : "Dawn",
      "company" : "Alpha Inc"
    },
    {
      "_id" : ObjectId("62442429854636a03f6b853a"),
      "name" : "Henry",
      "company" : "Alpha Inc",
      "telephone" : [
        { "home" : "789789789" }
      ],
    }
  ]
}
{
  "_id" : ObjectId("62442429854636a03f6b853c"),
  "name" : "Beta Inc",
  "base" : "US",
  "employee" : [
    {
      "_id" : ObjectId("62442429854636a03f6b8535"),
      "name" : "Brian",
      "company" : "Beta Inc"
    },
    {
      "_id" : ObjectId("62442429854636a03f6b8538"),
      "name" : "Esther",
      "company" : "Beta Inc",
      "telephone" : [
       { "home" : "001122334455" }
      ]
    }
  ]
}

Uma operação de $project (projeção) agora pode remover tudo, exceto as arrays de funcionários. Isso é seguido por uma operação de $unwind para destruir a array e obter documentos separados dos funcionários:

db.company.aggregate([
  { $match: { base: 'US' } },
  { $lookup: { from: 'person', localField: 'name', foreignField: 'company', as: 'employee' } },
  { $project: { _id: 0, employee: 1 } },
  { $unwind: '$employee' }
]);

O resultado:

{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b8534"),
    "name" : "Abdul",
    "company" : "Alpha Inc",
    "telephone" : [
      { "home" : "0123456789" },
      { "work" : "9876543210" }
    ]
  }
}
{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b8537"),
    "name" : "Dawn",
    "company" : "Alpha Inc"
  }
}
{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b853a"),
    "name" : "Henry",
    "company" : "Alpha Inc",
    "telephone" : [
      { "home" : "789789789" }
    ]
  }
}
{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b8535"),
    "name" : "Brian",
    "company" : "Beta Inc"
  }
}
{
  "employee" : {
    "_id" : ObjectId("62442429854636a03f6b8538"),
    "name" : "Esther",
    "company" : "Beta Inc",
    "telephone" : [
      { "home" : "001122334455" }
    ]
  }
}

Por fim, uma operação de $replaceRoot é usada para formatar cada documento para retornar apenas o nome da pessoa, empresa e número de telefone do trabalho. Isso é seguido por uma ordem de $sort para emitir documentos em ordem de ascendente de nome. A consulta agregada completa fica:

db.company.aggregate([
  { $match: { base: 'US' } },
  { $lookup: { from: 'person', localField: 'name', foreignField: 'company', as: 'employee' } },
  { $project: { _id: 0, employee: 1 } },
  { $unwind: '$employee' },
  { $replaceRoot: {
    newRoot: {
      $mergeObjects: [ {
        name: "$employee.name",
        company: '$employee.company',
        work: { $first: '$employee.telephone.work' }
      }, "$name" ]
   } } },
  { $sort: { name: 1 } }
]);

O resultado:

{
  "name" : "Abdul",
  "company" : "Alpha Inc",
  "work" : "9876543210"
}
{
  "name" : "Brian",
  "company" : "Beta Inc",
}
{
  "name" : "Dawn",
  "company" : "Alpha Inc",
}
{
  "name" : "Esther",
  "company" : "Beta Inc"
}
{
  "name" : "Henry",
  "company" : "Alpha Inc"
}

Existem outras maneiras de obter esse resultado, mas o ponto principal é que o MongoDB pode fazer a maior parte do trabalho. Raramente é necessário ler documentos e manipular os dados em seu código de aplicativo diretamente.

Como executar operações em massa do MongoDB

Por padrão, o MongoDB pode lidar com 1.000 operações simultâneas. Isso provavelmente não será um problema ao usar o mongosh, mas os aplicativos podem atingir esse limite se fizerem uma série de manipulações de dados em registros individuais. Aplicativos Node.js são especialmente problemáticas porque podem emitir rapidamente uma série de solicitações assíncronas sem precisar esperar até que elas sejam concluídas.

Para contornar este problema, MongoDB fornece uma API de operações em massa que aceita qualquer número de atualizações que podem ser executadas em ordem ou em qualquer ordem.

Aqui está um exemplo de pseudocódigo no Node.js:

// reference the mycollection collection
const bulk = db.collection('mycollection').initializeUnorderedBulkOp();

// make any number of data changes
bulk.insertOne(...);
bulk.insertMany(...)
bulk.updateOne(...);
bulk.deleteOne(...);
// etc...

bulk.execute();

O último comando efetivamente emite uma única solicitação MongoDB, então você tem menos chance de atingir o limite de 1.000 operações.

Resumo

O MongoDB oferece um armazenamento flexível para aplicativos, como sistemas de gerenciamento de conteúdo, agendas de endereços e redes sociais, onde estruturas de dados rígidas são muito restritivas e difíceis de definir. As escritas de dados são rápidas e a fragmentação entre múltiplos servidores se torna mais fácil.

Escrever aplicativos usando um banco de dados MongoDB também pode ser libertador. É possível armazenar quaisquer dados em qualquer documento em qualquer coleção, a qualquer momento. Isso é especialmente prático quando você está desenvolvendo um protótipo ou produto mínimo viável usando metodologias ágeis onde os requisitos evoluem com o tempo.

Dito isso, consultas complexas podem ser um desafio e os conceitos de desnormalização são difíceis de aceitar quando você está migrando do mundo SQL.

O MongoDB é menos adequado para aplicativos com requisitos transacionais rigorosos onde a integridade dos dados é essencial, como bancos, contabilidade e sistemas de controle de estoque. Esses sistemas têm campos de dados identificáveis que devem ser projetados antes do início da codificação.

Existem muitos tipos de aplicativos entre esses dois extremos, o que dificulta escolher um banco de dados apropriado. Felizmente, bancos de dados NoSQL, incluindo o MongoDB, começaram a adotar opções semelhantes ao SQL, incluindo JOINs e transações.

Por outro lado, bancos de dados SQL como MySQL e PostgreSQL agora oferecem campos de dados JSON do tipo NoSQL. Eles também podem merecer sua atenção, mas como sempre, a escolha final é sua.