Dia 04 de 15

Consulta a Estruturas Complexas

Olá, dev! Seja bem-vindo ao Dia 04 do seu plano de preparação para a certificação MongoDB Associate Developer. O tema de hoje é um dos mais cobrados na prova: como consultar dados aninhados (Embedded Documents) e coleções de elementos (Arrays), dominando a Notação de Ponto e o uso preciso do operador $elemMatch.

1. Consultando Documentos Embutidos (Embedded Documents)

Documentos embutidos permitem armazenar dados relacionados diretamente no mesmo documento pai. Existem duas maneiras de consultar subdocumentos, com comportamentos drasticamente diferentes:

A. Correspondência Exata de Subdocumento (Exact Embedded Match)

// Exige que o subdocumento 'endereco' seja EXATAMENTE igual, na mesma ordem de campos
db.usuarios.find({
  endereco: { cidade: "Uberaba", estado: "MG" }
})

Armadilha do Exame: Se o documento no banco for { endereco: { estado: "MG", cidade: "Uberaba" } } (ordem invertida) ou possuir um campo extra como pais: "Brasil", a consulta acima não retornará nada.

B. Notação de Ponto (Dot Notation)

Para consultar subcampos de forma flexível e independente da ordem ou de outros campos presentes, envolva o caminho do campo entre aspas:

// Busca qualquer usuario que tenha cidade = "Uberaba", independentemente da ordem ou outros campos
db.usuarios.find({
  "endereco.cidade": "Uberaba"
})

2. Consultas a Arrays Simples

A. Correspondência de Elemento Único e Correspondência Exata

// Retorna documentos onde o array 'habilidades' CONTÉM o elemento "MongoDB"
db.developers.find({ habilidades: "MongoDB" })

// Retorna documentos onde o array 'habilidades' é EXATAMENTE igual a ["MongoDB", "Kotlin"] (mesma ordem e tamanho)
db.developers.find({ habilidades: ["MongoDB", "Kotlin"] })

B. Operadores `$all` e `$size`

// Busca desenvolvedores que possuem TANTO "MongoDB" QUANTO "Kotlin" no array habilidades
db.developers.find({ habilidades: { $all: ["MongoDB", "Kotlin"] } })

// Busca desenvolvedores com exatamente 3 habilidades cadastradas
db.developers.find({ habilidades: { $size: 3 } })

C. Acesso por Índice do Array

Utilize a Notação de Ponto com a posição numérica do elemento:

// Busca documentos onde a PRIMEIRA habilidade (índice 0) é "Kotlin"
db.developers.find({ "habilidades.0": "Kotlin" })

3. O Poderoso Operador `$elemMatch` em Arrays de Objetos

Quando um array armazena objetos/documentos embutidos, surge uma armadilha comum. Suponha a coleção de pedidos:

{
  _id: 101,
  cliente: "Ada Lovelace",
  itens: [
    { produto: "Teclado Mecânico", qtd: 2, preco: 350 },
    { produto: "Mousepad XL", qtd: 10, preco: 80 }
  ]
}

O Problema sem `$elemMatch`:

// Consulta SEM $elemMatch:
db.pedidos.find({
  "itens.qtd": { $gt: 5 },
  "itens.preco": { $lt: 100 }
})

Esta consulta retornará o documento acima! Por quê? O MongoDB verifica se algum item tem qtd > 5 (o Mousepad tem) e se algum item tem preco < 100 (o Mousepad tem). As condições são satisfeitas no array, mas não necessariamente no mesmo elemento.

A Solução com `$elemMatch`:

O operador $elemMatch obriga o MongoDB a encontrar pelo menos um único elemento do array que satisfaça TODAS as condições simultaneamente:

db.pedidos.find({
  itens: {
    $elemMatch: {
      qtd: { $gt: 5 },
      preco: { $lt: 100 }
    }
  }
})

4. Projeções com Arrays

Além de filtrar documentos, é possível controlar quais elementos de um array são retornados na resposta:

A. Projeção com `$elemMatch`

Retorna no resultado apenas o primeiro elemento do array que atende à condição especificada:

db.pedidos.find(
  { cliente: "Ada Lovelace" },
  { cliente: 1, itens: { $elemMatch: { preco: { $gt: 300 } } } }
)

B. Operador Posicional de Projeção `$`

Retorna o primeiro elemento do array que coincidiu com o filtro da consulta principal:

db.pedidos.find(
  { "itens.produto": "Mousepad XL" },
  { cliente: 1, "itens.$": 1 }
)

C. Operador `$slice`

Limita a quantidade de elementos retornados de um array:

// Retorna apenas os últimos 2 itens do array
db.pedidos.find({}, { cliente: 1, itens: { $slice: -2 } })

// Pula 1 elemento e retorna os próximos 2
db.pedidos.find({}, { cliente: 1, itens: { $slice: [1, 2] } })

5. Questões no Estilo Exame de Certificação

Questão 1: Considere o documento: { _id: 1, alunos: [{ nome: "Ana", nota: 8 }, { nome: "Bruno", nota: 5 }] }. Qual das consultas a seguir retornará este documento?

A) db.turmas.find({ "alunos.nome": "Ana", "alunos.nota": 5 })
B) db.turmas.find({ alunos: { $elemMatch: { nome: "Ana", nota: 5 } } })
C) db.turmas.find({ alunos: { $size: { $gte: 2 } } })
D) db.turmas.find({ "alunos.0.nota": 5 })

Resposta Correta: A. Sem o $elemMatch, a consulta A verifica se existe algum elemento com nome "Ana" (existe) e algum elemento com nota 5 (Bruno tem). As condições são satisfeitas separadamente e o documento é retornado. A opção B não retorna pois nenhum aluno individual possui nome "Ana" AND nota 5. C lança erro pois $size não aceita operadores de comparação.

Questão 2: Como consultar uma coleção de cursos para encontrar documentos onde o array topicos contenha os valores "BSON" e "Indexação", independentemente de outros tópicos e da ordem?

A) db.cursos.find({ topicos: ["BSON", "Indexação"] })
B) db.cursos.find({ topicos: { $all: ["BSON", "Indexação"] } })
C) db.cursos.find({ "topicos.$": ["BSON", "Indexação"] })
D) db.cursos.find({ topicos: { $elemMatch: ["BSON", "Indexação"] } })

Resposta Correta: B) `topicos: { $all: ["BSON", "Indexação"] }`. O operador $all garante a presença de todos os itens informados no array sem impor ordem ou exclusividade.