Manipulação de dados e consultas em MongoDB

1. Introdução ao MongoDB e seu modelo de dados

O MongoDB é um banco de dados NoSQL orientado a documentos que armazena dados em formato BSON (Binary JSON), diferentemente das tabelas relacionais tradicionais. Enquanto bancos relacionais organizam dados em linhas e colunas com esquemas rígidos, o MongoDB utiliza coleções que contêm documentos flexíveis, onde cada documento pode ter uma estrutura diferente.

Cada documento possui um campo _id obrigatório, que funciona como chave primária. Se não for especificado, o MongoDB gera automaticamente um ObjectId único. Os tipos de dados suportados incluem strings, números, booleanos, arrays, objetos aninhados, datas, ObjectId, entre outros. O aninhamento de documentos permite representar relacionamentos complexos sem a necessidade de JOINs.

// Exemplo de documento BSON
{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "nome": "Maria Silva",
  "idade": 34,
  "endereco": {
    "rua": "Av. Paulista",
    "numero": 1000,
    "cidade": "São Paulo"
  },
  "telefones": ["11999999999", "11888888888"]
}

2. Operações básicas de CRUD

Inserção

A inserção de documentos pode ser feita individualmente ou em lote. O MongoDB valida automaticamente a estrutura do documento, mas não exige um esquema pré-definido.

// Inserir um documento
db.usuarios.insertOne({
  "nome": "João",
  "email": "joao@email.com",
  "idade": 28
})

// Inserir múltiplos documentos
db.usuarios.insertMany([
  { "nome": "Ana", "email": "ana@email.com", "idade": 32 },
  { "nome": "Carlos", "email": "carlos@email.com", "idade": 25 }
])

Leitura

O método find() permite consultar documentos com projeção de campos e limitação de resultados.

// Buscar todos os documentos
db.usuarios.find()

// Buscar com projeção (apenas nome e idade)
db.usuarios.find({}, { "nome": 1, "idade": 1, "_id": 0 })

// Limitar resultados
db.usuarios.find().limit(5)

Atualização

Operadores como $set, $unset e $inc permitem modificar documentos de forma precisa.

// Atualizar um campo específico
db.usuarios.updateOne(
  { "nome": "João" },
  { $set: { "idade": 29 } }
)

// Incrementar um valor numérico
db.usuarios.updateMany(
  { "idade": { $gte: 30 } },
  { $inc: { "idade": 1 } }
)

// Remover um campo
db.usuarios.updateOne(
  { "nome": "Ana" },
  { $unset: { "telefone": "" } }
)

Remoção

A remoção deve ser feita com cuidado, especialmente em operações em massa.

// Remover um documento
db.usuarios.deleteOne({ "nome": "Carlos" })

// Remover múltiplos documentos
db.usuarios.deleteMany({ "idade": { $lt: 18 } })

3. Consultas com filtros e operadores de comparação

Os operadores de comparação permitem filtrar documentos com base em condições específicas.

// Operadores básicos
db.produtos.find({ "preco": { $eq: 100 } })        // Igual
db.produtos.find({ "preco": { $ne: 100 } })        // Diferente
db.produtos.find({ "preco": { $gt: 50 } })         // Maior que
db.produtos.find({ "preco": { $gte: 50 } })        // Maior ou igual
db.produtos.find({ "preco": { $lt: 100 } })        // Menor que
db.produtos.find({ "preco": { $lte: 100 } })       // Menor ou igual

// Operadores lógicos
db.produtos.find({
  $and: [
    { "preco": { $gte: 50 } },
    { "categoria": "eletrônicos" }
  ]
})

db.produtos.find({
  $or: [
    { "preco": { $lt: 20 } },
    { "categoria": "promoção" }
  ]
})

// Operadores de array
db.produtos.find({ "tags": { $in: ["urgente", "destaque"] } })
db.produtos.find({ "tags": { $all: ["novo", "coleção"] } })
db.produtos.find({ "avaliacoes": { $elemMatch: { "nota": { $gte: 4 } } } })

4. Trabalhando com arrays e documentos aninhados

A navegação em subdocumentos utiliza dot notation, e a manipulação de arrays é feita com operadores específicos.

// Consulta em subdocumento
db.pedidos.find({ "endereco.entrega.cidade": "São Paulo" })

// Adicionar elemento ao array
db.usuarios.updateOne(
  { "nome": "Maria" },
  { $push: { "telefones": "11777777777" } }
)

// Remover elemento do array
db.usuarios.updateOne(
  { "nome": "Maria" },
  { $pull: { "telefones": "11888888888" } }
)

// Adicionar elementos únicos
db.usuarios.updateOne(
  { "nome": "Pedro" },
  { $addToSet: { "interesses": "futebol" } }
)

// Atualizar elemento específico em array
db.pedidos.updateOne(
  { "itens.nome": "Notebook" },
  { $set: { "itens.$.quantidade": 3 } }
)

5. Agregação e pipeline de dados

O pipeline de agregação permite processar dados em múltiplos estágios.

// Pipeline básico de agregação
db.vendas.aggregate([
  { $match: { "data": { $gte: ISODate("2024-01-01") } } },
  { $group: {
      _id: "$categoria",
      totalVendas: { $sum: "$valor" },
      mediaPreco: { $avg: "$preco" },
      quantidade: { $count: {} }
  }},
  { $sort: { "totalVendas": -1 } },
  { $project: {
      _id: 0,
      categoria: "$_id",
      totalVendas: 1,
      mediaPreco: { $round: ["$mediaPreco", 2] }
  }}
])

// Desaninhar arrays com $unwind
db.pedidos.aggregate([
  { $unwind: "$itens" },
  { $group: {
      _id: "$itens.nome",
      totalVendido: { $sum: "$itens.quantidade" }
  }}
])

// Adicionar campos calculados
db.produtos.aggregate([
  { $addFields: {
      precoComDesconto: { $multiply: ["$preco", 0.9] },
      categoriaUpper: { $toUpper: "$categoria" }
  }}
])

6. Indexação e otimização de consultas

Índices são essenciais para performance em consultas frequentes.

// Criar índice simples
db.usuarios.createIndex({ "email": 1 })

// Criar índice composto
db.pedidos.createIndex({ "cliente_id": 1, "data": -1 })

// Criar índice textual
db.produtos.createIndex({ "descricao": "text" })

// Analisar performance com explain()
db.usuarios.find({ "email": "joao@email.com" }).explain("executionStats")

// Criar índice TTL (expiração automática)
db.sessoes.createIndex({ "criadoEm": 1 }, { expireAfterSeconds: 3600 })

7. Transações e consistência de dados

Transações multi-documento garantem atomicidade em operações complexas.

// Transação multi-documento
const session = db.getMongo().startSession()
session.startTransaction()

try {
  const contas = session.getDatabase("banco").contas

  contas.updateOne(
    { "conta": "A001" },
    { $inc: { "saldo": -500 } }
  )

  contas.updateOne(
    { "conta": "B002" },
    { $inc: { "saldo": 500 } }
  )

  session.commitTransaction()
} catch (error) {
  session.abortTransaction()
} finally {
  session.endSession()
}

// Configurar write concern para consistência
db.produtos.insertOne(
  { "nome": "Produto X" },
  { writeConcern: { w: "majority", j: true } }
)

8. Boas práticas e padrões de modelagem

A escolha entre documentos embutidos e referências impacta diretamente a performance.

// Modelagem embeddable (um-para-um)
db.clientes.insertOne({
  "nome": "João",
  "endereco": {
    "rua": "Rua A",
    "cidade": "São Paulo",
    "cep": "01001-000"
  }
})

// Modelagem referencial (um-para-muitos)
db.pedidos.insertOne({
  "cliente_id": ObjectId("507f1f77bcf86cd799439011"),
  "itens": [
    { "produto_id": ObjectId("507f1f77bcf86cd799439012"), "quantidade": 2 }
  ]
})

// Schema validation
db.createCollection("produtos", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["nome", "preco"],
      properties: {
        nome: { bsonType: "string" },
        preco: { bsonType: "double", minimum: 0 }
      }
    }
  }
})

// Bucket pattern para séries temporais
db.sensores.insertMany([
  {
    "sensor_id": "S001",
    "leituras": [
      { "timestamp": ISODate("2024-01-01T10:00:00Z"), "valor": 25.5 },
      { "timestamp": ISODate("2024-01-01T10:05:00Z"), "valor": 26.1 }
    ]
  }
])

Referências