MongoDB: Noções básicas sobre consultas a documentos

Última atualização: 2026-08-26

As consultas são as operações fundamentais para recuperar dados do MongoDB — dominar o método find é o primeiro passo para interagir com o banco de dados.

Este curso oferece uma análise aprofundada da sintaxe de consulta find e findOne, da projeção de campos, da paginação e ordenação, bem como da formatação e do processamento dos resultados das consultas.

1. O que você vai aprender


2. A história real de um engenheiro full-stack

(1) Problema: Consultas que retornam todos os campos resultam em alta sobrecarga de rede

Charlie é um engenheiro full-stack de comércio eletrônico que está otimizando o desempenho da API da lista de produtos:

“A API da minha lista de produtos retorna 100 produtos, cada um com 5 KB, mas o front-end exibe apenas três campos: título, preço e imagem. Ela retorna 50 KB de dados desnecessários, e a resposta da API leva 800 ms, desperdiçando largura de banda e tempo de análise.”

Código da consulta original:

JAVASCRIPT
// ❌ Counterexample:Return all fields
app.get('/api/products', async (req, res) => {
  const products = await Product.find();  // Return all fields
  res.json(products);
});
// Each product 5KB,100 items = 500KB
// Slow Internet Connection + Slow front-end parsing

(2) Solução para projeção

JAVASCRIPT
// ✅ Correct Example:Return only the necessary fields
app.get('/api/products', async (req, res) => {
  const products = await Product.find(
    { isActive: true },
    {
      projection: {
        sku: 1,
        title: 1,
        price: 1,
        thumbnail: 1,
        _id: 0   // Exclusion _id
      }
    }
  )
    .sort({ createdAt: -1 })
    .limit(20)
    .lean();   // Skip mongoose hydrate,Performance ↑3-5x

  res.json(products);
});
// Each product 200 Byte,20 items = 4KB(Performance ↑100x)

(3) Receita

Dimensão Não projetado Projetado
Tamanho da resposta 500 KB 4 KB
Latência da API 800 ms 50 ms
Tempo de análise do front-end 200 ms 5 ms
Largura de banda da rede Alta Baixa (100x)

3. find e findOne

Explicação do conceito: find e findOne são os dois principais métodos de consulta no MongoDB. find retorna um cursor com os documentos correspondentes e é adequado para consultas de lista; findOne retorna um único documento e é adequado para consultas detalhadas. Embora sua sintaxe seja semelhante, seus tipos de retorno diferem; compreender essa diferença é fundamental para lidar corretamente com os resultados das consultas.

Como funciona: find não retorna todos os dados imediatamente; em vez disso, cria um objeto Cursor. O Cursor utiliza uma estratégia de carregamento diferido — os dados são buscados do servidor em lotes (101 registros ou 1 MB por lote, por padrão) somente quando você itera por ele ou chama toArray(). Esse design garante que find não esgote a memória, mesmo com conjuntos de dados na casa dos milhões. findOne é equivalente a find().limit(1), mas retorna documentos diretamente, em vez de um Cursor.

100%
sequenceDiagram
    participant App as Applications
    participant Mongo as MongoDB Server-side

    App->>Mongo: find({ category: "Electronics" })
    Mongo-->>App: Cursor Object(No data retrieved)
    App->>Mongo: cursor.next() / toArray()
    Mongo-->>App: The first batch 101 Documents
    App->>Mongo: Continue iterating
    Mongo-->>App: Subsequent batches(Maximum per batch 16MB)
Dimensão find findOne
Tipo de retorno Cursor Documento ou nulo
Número de partidas Todas as partidas Primeira partida
Uso de memória Streaming (carregamento diferido) Único
Desempenho Rápido Um pouco mais rápido (não cria um cursor)
Casos de uso Consulta de lista Consulta de detalhes

(1) Como usar find para consultar vários documentos

JAVASCRIPT
// === find Basic Syntax ===
db.products.find();
// Back to All Documents(cursor)

// === find Specified Conditions ===
db.products.find({ category: "Electronics" });
// Back to All Electronics

// === find Return an array ===
db.products.find({ category: "Electronics" }).toArray();
// Back Array<Document>

// === find Iterate(Cursor)===
db.products.find({ category: "Electronics" }).forEach(printjson);

(2) findOne: Consultar um único documento

Explicação do conceito: findOne é uma maneira prática de consultar um único documento; internamente, é equivalente a find().limit(1), mas retorna diretamente um objeto de documento, em vez de um Cursor. O retorno de null indica que nenhum documento correspondente foi encontrado — essa é uma distinção importante em relação a find, já que find retorna um Cursor vazio, em vez de null.

Casos de uso: Consultar detalhes usando _id, recuperar um único documento usando um índice exclusivo e realizar uma verificação de existência (para determinar se um documento atende a uma condição específica).

JAVASCRIPT
// === findOne Return to a single document ===
db.products.findOne({ sku: "PHONE-001" });
// Return the first matching document(or null)

// === findOne vs find().limit(1) Difference ===
const doc1 = db.products.findOne({ sku: "PHONE-001" });
const doc2 = db.products.find({ sku: "PHONE-001" }).limit(1).next();
// The results are the same,findOne More concise

(3) Comparação entre find e findOne

Análise dos pontos-chave:

  1. find O Cursor retornado não carrega todos os dados imediatamente, economizando memória
  2. findOne é, essencialmente, find().limit(-1); ele retorna o documento diretamente, eliminando a necessidade de criar um Cursor.
  3. No Mongoose, find retorna um array Array<T>, e findOne retorna um objeto T | null
  4. Para determinar se um documento existe, findOne + verificação de valor nulo é mais eficiente do que find + verificação do comprimento da matriz.
Dimensão find findOne
Tipo de retorno Cursor Documento ou nulo
Número de partidas Todas as partidas Primeira partida
Uso de memória Streaming (carregamento diferido) Único
Desempenho Rápido Um pouco mais rápido (não cria um cursor)
Casos de uso Consulta de lista Consulta de detalhes

(4) Resultados de consultas no Mongoose

JAVASCRIPT
// === mongoose: find returns an array ===
const products = await Product.find({ category: "Electronics" });
// Array<Product>

// === mongoose: findOne returns an object ===
const product = await Product.findOne({ sku: "PHONE-001" });
// Product | null

// === Handling Cases Where Query Results Are Empty ===
const product = await Product.findOne({ sku: "NOT_EXIST" });
if (!product) {
  return res.status(404).json({ error: "Product not found" });
}

▶ Exemplo 1: Uso completo de find

JAVASCRIPT
// === Search in mongosh ===
// Search All Documents
db.products.find();

// Query by Specified Criteria
db.products.find({ category: "Electronics" });

// Multi-Condition Query(AND)
db.products.find({
  category: "Electronics",
  stock: { $gt: 0 }     // Inventory greater than 0
});

// Look Up and Format
db.products.find({ category: "Electronics" }).pretty();

// Query and Count
db.products.find({ category: "Electronics" }).count();

// === Search in Node.js ===
const { MongoClient } = require('mongodb');

async function findProducts() {
  const client = new MongoClient('mongodb://localhost:27017');
  await client.connect();
  const collection = client.db('shopdb').collection('products');

  // 1. find() Back Cursor
  const cursor = collection.find({ category: "Electronics" });
  const products = await cursor.toArray();
  console.log(`Found ${products.length} products`);

  // 2. findOne() Back Document
  const product = await collection.findOne({ sku: "PHONE-001" });
  console.log(product);

  // 3. Iterate Cursor(Flow)
  for await (const doc of collection.find({ category: "Electronics" })) {
    console.log(doc.title);
  }

  await client.close();
}

findProducts();

4. Filtros de consulta

Explicação do conceito: O filtro de consulta é o primeiro parâmetro de find / findOne e é usado para especificar as condições de correspondência. Os filtros utilizam a sintaxe JSON/BSON e suportam vários padrões, incluindo correspondências exatas, operadores de comparação, combinações lógicas e consultas aninhadas. Compreender a sintaxe do filtro é a base das consultas no MongoDB.

Como funciona: O MongoDB converte os filtros de consulta em um plano de consulta e faz a correspondência entre documentos usando índices ou varreduras completas da tabela. Cada condição de campo em um filtro pode utilizar um índice de forma independente; quando várias condições são combinadas, o otimizador do MongoDB seleciona automaticamente o caminho de execução ideal.

100%
graph TB
    A[Query Filters] --> B[Exact Match<br/>{ field: value }]
    A --> C[Comparison Operators<br/>{ field: { $gt: N } }]
    A --> D[Logic Combinations<br/>{ $and / $or / $not }]
    A --> E[Nested Queries<br/>{ "path.field": value }]
    A --> F[Array Lookup<br/>{ array: value }]

    style A fill:#cce5ff
Tipo de filtro Sintaxe Exemplo
Correspondência exata { field: value } { sku: "PHONE-001" }
Condição múltipla AND { f1: v1, f2: v2 } { category: "E", stock: 50 }
O campo não existe { field: { $exists: false } } { discount: { $exists: false } }
Documento aninhado { "path.field": value } { "specs.battery": "4500mAh" }
Elemento da matriz { array: value } { tags: "5g" }

(1) Filtragem básica

JAVASCRIPT
// === Exact Match ===
db.products.find({ sku: "PHONE-001" });

// === Multiple conditions AND ===
db.products.find({
  category: "Electronics",
  stock: 50,
  isActive: true
});

// === Field does not exist ===
db.products.find({ discount: { $exists: false } });

// === Nested Document Query ===
db.products.find({ "specs.battery": "4500mAh" });

// === Array Element Matching ===
db.products.find({ tags: "5g" });

// === Matching Multiple Elements in an Array ===
db.products.find({ tags: { $all: ["5g", "amoled"] } });

(2) Operadores de comparação

Visão geral do conceito: Os operadores de comparação são o cerne dos filtros de consulta, permitindo operações como consultas por intervalo, correspondência de múltiplos valores e exclusões. O MongoDB oferece oito operadores de comparação: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin. Dentre eles, $eq é o comportamento padrão ({ price: 599 } é equivalente a { price: { $eq: 599 } }), e $in é o operador mais comumente utilizado.

Operador Significado SQL equivalente Compatível com índices
$eq é igual a WHERE field = value
$ne Diferente de WHERE field != value ⚠️
$gt/$gte Maior que / Maior ou igual a WHERE field > />= value
$lt/$lte Menor que / Menor ou igual a WHERE field < /<= value
$in Incluído em WHERE field IN (...)
$nin Não incluído WHERE field NOT IN (...) ⚠️
JAVASCRIPT
// === $eq(equals,Default)===
db.products.find({ price: { $eq: 599.99 } });
// equivalent to { price: 599.99 }

// === $ne(is not equal to)===
db.products.find({ category: { $ne: "Books" } });

// === $gt / $gte(greater than / Greater than or equal to)===
db.products.find({ price: { $gt: 100 } });        // > 100
db.products.find({ price: { $gte: 100 } });       // >= 100

// === $lt / $lte(Less than / Less than or equal to)===
db.products.find({ price: { $lt: 1000 } });
db.products.find({ price: { $lte: 1000 } });

// === $in / $nin(Includes / Excludes)===
db.products.find({ category: { $in: ["Electronics", "Books"] } });
db.products.find({ category: { $nin: ["Clothing"] } });

// === Range Query ===
db.products.find({
  price: { $gte: 100, $lte: 1000 }  // 100 <= price <= 1000
});

(3) Operadores lógicos

Explicação do conceito: Os operadores lógicos combinam várias condições de consulta para implementar uma lógica de filtragem complexa. O MongoDB suporta quatro operadores lógicos: $and (todas as condições devem ser atendidas), $or (qualquer condição deve ser atendida), $not (nenhuma das condições deve ser atendida) e $nor (nenhuma das condições deve ser atendida). Dentre eles, o AND implícito (separando vários campos por vírgulas) é a sintaxe mais comumente usada; o $and explícito é necessário apenas quando “várias condições se aplicam ao mesmo campo”.

Operador Significado SQL equivalente Frequência de uso
AND implícito Separado por vírgulas WHERE a=1 AND b=2 ⭐⭐⭐ Mais utilizado
$and E explícito WHERE (a=1 AND b=2) ⭐ Várias condições para o mesmo campo
$or Qualquer um que atenda aos requisitos WHERE a=1 OR b=2 ⭐⭐
$not Insatisfeito WHERE NOT (condition)
$nor Nenhum atendido WHERE NOT (a=1 OR b=2) Subutilizado
JAVASCRIPT
// === $and(Implicit AND)===
db.products.find({
  category: "Electronics",
  stock: { $gt: 0 }    // Implicit AND
});

// === $and(Explicit AND)===
db.products.find({
  $and: [
    { category: "Electronics" },
    { $or: [{ stock: { $gt: 10 } }, { isFeatured: true }] }
  ]
});

// === $or ===
db.products.find({
  $or: [
    { category: "Electronics" },
    { tags: "bestseller" }
  ]
});

// === $not ===
db.products.find({ price: { $not: { $gt: 1000 } } });
// Price <= 1000

// === $nor(None of them match)===
db.products.find({
  $nor: [
    { category: "Electronics" },
    { category: "Books" }
  ]
});

▶ Exemplo 2: Exemplo de uma consulta composta

JAVASCRIPT
// === Scene:Check Prices 100-1000、Inventory greater than 0、Electronics or Books Categorized Products ===
db.products.find({
  price: { $gte: 100, $lte: 1000 },
  stock: { $gt: 0 },
  $or: [
    { category: "Electronics" },
    { category: "Books" }
  ],
  isActive: true
}).sort({ price: 1 }).limit(20);

// === mongoose Equivalent Notation ===
const products = await Product.find({
  price: { $gte: 100, $lte: 1000 },
  stock: { $gt: 0 },
  $or: [{ category: "Electronics" }, { category: "Books" }],
  isActive: true
})
  .sort({ price: 1 })
  .limit(20)
  .lean();

5. Projeção

Explicação do conceito: A projeção controla quais campos são retornados por uma consulta e é uma técnica fundamental de otimização para reduzir o tráfego de rede e a carga na análise do front-end. Por padrão, o MongoDB retorna todos os campos de um documento, mas em cenários como páginas de lista e respostas de API, normalmente são necessários apenas 3 a 5 campos-chave. O uso adequado da projeção pode reduzir o tamanho da resposta em mais de 90%.

Como funciona: A projeção é realizada no lado do servidor — depois que o MongoDB lê o documento inteiro, ele remove os campos de acordo com as regras de projeção antes de retornar o resultado. Isso significa que a projeção não reduz a E/S do disco (o documento inteiro ainda precisa ser lido), mas pode reduzir significativamente o tráfego de rede e o tempo de desserialização do cliente. A única exceção é uma consulta coberta — quando todos os campos da consulta e da projeção estão incluídos em um índice, o MongoDB retorna os dados diretamente do índice, sem precisar ler o documento.

100%
graph LR
    A[Complete Documentation<br/>20 field ~5KB] --> B{Projection Rules}
    B -->|Whitelist Mode<br/>{ sku: 1, title: 1, price: 1 }| C[3 field ~200B]
    B -->|Blacklist Mode<br/>{ description: 0, images: 0 }| D[18 field ~4.5KB]
    
    style C fill:#d4edda
Modo de projeção Sintaxe Recursos Casos de uso
Lista de permissões { field: 1 } Retorna apenas os campos especificados Página de lista (requer poucos campos)
Lista negra { field: 0 } Excluir campos especificados Página de detalhes (Excluir campos confidenciais)
Misto ❌ Não permitido Listas de permissões e listas de restrições não podem ser usadas juntas (exceto para _id)
_id Control { _id: 0 } Retornado por padrão; deve ser explicitamente excluído Remover _id da resposta da API

(1) O que é uma projeção?

O controle de projeção retorna campos específicos, reduzindo a carga na transmissão de rede e na análise do front-end.

100%
graph LR
    A[Complete Documentation<br/>20 field] --> B{Projection}
    B -->|Field Whitelist| C[Return only 3 field<br/>~10 KB]
    B -->|Field Blacklist| D[Exclusion 2 field<br/>~18 KB]

    style C fill:#d4edda

(2) Sintaxe de projeção

JAVASCRIPT
// === Field Whitelist(Return only the specified fields)===
db.products.find(
  { category: "Electronics" },
  { sku: 1, title: 1, price: 1 }
);
// Back:{ _id, sku, title, price }

// === _id Return by Default,Must be explicitly excluded ===
db.products.find(
  {},
  { sku: 1, title: 1, _id: 0 }  // _id: 0 Exclusion _id
);

// === Field Blacklist(Exclude Specified Fields)===
db.products.find(
  {},
  { internalNotes: 0, debugInfo: 0 }  // Exclude Sensitive Fields
);

// === Nested Document Projection ===
db.products.find(
  { sku: "PHONE-001" },
  {
    sku: 1,
    title: 1,
    "specs.screen": 1,    // Return only specs.screen
    "specs.battery": 1   // Return only specs.battery
  }
);

// === Array Element Projection($slice)===
db.reviews.find(
  { productId: "PHONE-001" },
  {
    title: 1,
    content: 1,
    comments: { $slice: 3 }  // Return only the first 3 Comments
  }
);

(3) Efeitos no desempenho da projeção

JAVASCRIPT
// === Performance Testing:100 10,000 Documents,Search 100 items ===

// ❌ No projection:Back 5MB
db.products.find({ category: "Electronics" }).limit(100);
// Time taken 800ms

// ✅ Projection available:Back 200KB
db.products.find(
  { category: "Electronics" },
  { sku: 1, title: 1, price: 1, _id: 0 }
).limit(100);
// Time taken 80ms(Performance ↑10x)

(4) Projeção no Mongoose

JAVASCRIPT
// === Methods 1:projection option ===
const products = await Product.find({ category: "Electronics" }, "sku title price");
// String Syntax(Space-separated)

// === Methods 2:select() Chain-type ===
const products = await Product.find()
  .select("sku title price")
  .select("-description -images");  // Exclude certain fields

// === Methods 3:Object Syntax ===
const products = await Product.find(
  { category: "Electronics" },
  { sku: 1, title: 1, price: 1, _id: 0 }
);

// === Methods 4:lean() + select() Optimal Performance ===
const products = await Product.find()
  .select("sku title price")
  .lean()  // Skip mongoose hydrate
  .limit(100);

▶ Exemplo 3: Melhores práticas para APIs de listas de comércio eletrônico

JAVASCRIPT
// === Complete List of E-commerce Sites API ===
app.get('/api/products', async (req, res) => {
  const {
    category,
    minPrice,
    maxPrice,
    search,
    sort = 'createdAt',
    order = 'desc',
    page = 1,
    limit = 20
  } = req.query;

  // 1. Build Query Criteria
  const query = { isActive: true };
  if (category) query.category = category;
  if (minPrice || maxPrice) {
    query.price = {};
    if (minPrice) query.price.$gte = NumberDecimal(minPrice);
    if (maxPrice) query.price.$lte = NumberDecimal(maxPrice);
  }
  if (search) query.title = new RegExp(search, 'i');

  // 2. Sort
  const sortObj = { [sort]: order === 'desc' ? -1 : 1 };

  // 3. Pagination
  const skip = (page - 1) * limit;

  // 4. Search(With Projector + lean)
  const products = await Product.find(query)
    .select('sku title price thumbnail rating reviewCount')  // Return only 6 field
    .sort(sortObj)
    .skip(skip)
    .limit(Number(limit))
    .lean();   // Key:Skip mongoose hydrate

  // 5. Total Count
  const total = await Product.countDocuments(query);

  res.json({
    products,
    pagination: {
      page: Number(page),
      limit: Number(limit),
      total,
      pages: Math.ceil(total / limit)
    }
  });
});

6. pretty() e formatação de resultados

Explicação do conceito: pretty() é um método de formatação no mongosh que converte a saída JSON compacta em um formato legível e indentado. Ele não afeta a lógica da consulta nem os dados retornados; apenas altera a forma como a saída é exibida no terminal do mongosh. Na execução de scripts e no código Node.js, pretty() não funciona — é necessário usar printjson() ou JSON.stringify(obj, null, 2) para obter um efeito semelhante.

Método de formatação Ambiente Descrição
.pretty() modo de interação do mongosh Layout com recuo, melhor legibilidade
printjson() script mongosh Gera uma estrutura JSON completa
JSON.stringify(obj, null, 2) Node.js Formatação padrão de JSON
console.dir(obj, { depth: null }) Node.js Saída completa de estruturas profundamente aninhadas

(1) Formatação da saída com pretty()

JAVASCRIPT
// === Default Output (compact)===
db.products.findOne({ sku: "PHONE-001" });
// { _id: ObjectId('...'), sku: 'PHONE-001', title: 'Phone', ... }

// === pretty() Format ===
db.products.findOne({ sku: "PHONE-001" }).pretty();
// {
//   _id: ObjectId('507f1f77bcf86cd799439011'),
//   sku: 'PHONE-001',
//   title: 'Smartphone X',
//   price: NumberDecimal('599.99'),
//   ...
// }

// === find Also supported pretty ===
db.products.find({ category: "Electronics" }).pretty();

(2) O impacto da palavra “bonita” nos roteiros

BASH
# pretty Active in interactive mode,No differences in the script output
mongosh "mongodb://localhost:27017" --eval "db.products.find().pretty()"

(3) Formatação personalizada

JAVASCRIPT
// === Usage printjson() ===
db.products.find().forEach(printjson);
// Output the complete JSON Structure

// === Usage tojson() ===
const doc = db.products.findOne();
print(tojson(doc));

// === Format the output(pretty 2)===
printjson(doc, null, 2);

7. limitar / pular / ordenar

Explicação do conceito: limit, skip e sort são as três principais formas de modificar os resultados da consulta; elas controlam, respectivamente, o número de resultados retornados, o número de resultados a serem ignorados e a regra de ordenação. A ordem em que são aplicadas é sort → skip → limit, independentemente da ordem em que estão escritas no código — o MongoDB sempre ordena primeiro, pula resultados em seguida e, por fim, limita o número de resultados.

Como funciona: sort Exige que o MongoDB classifique os documentos correspondentes antes de retornar os resultados. Se o campo de classificação estiver indexado, ele usa a ordem do índice (altamente eficiente); caso contrário, a classificação é feita na memória (ocorrerá um erro se o tamanho exceder 32 MB). skip(N) Exige a varredura dos primeiros N documentos e seu descarte; quanto maior for N, pior será o desempenho — essa é a causa principal do problema de paginação profunda. limit(N) Limita o número de documentos retornados, permitindo que a varredura seja encerrada mais cedo.

100%
graph TB
    A[Query Results Set<br/>1000 Matches found] --> B[sort Sort<br/>By specified field]
    B --> C[skip Skip<br/>First N items]
    C --> D[limit Excerpt<br/>Return M items]
    
    B --> B1{The sort field is indexed?}
    B1 -->|Have| B2[Index Scan<br/>O(log N)]
    B1 -->|No| B3[Memory Sorting<br/>O(N log N)<br/>More than 32MB throws error]
    
    style B2 fill:#d4edda
    style B3 fill:#f8d7da
Método Função Impacto no desempenho Precauções
sort({ field: 1/-1 }) Classificação Classificação na memória sem índice 1: ascendente, -1: descendente
skip(N) Pular as primeiras N entradas Quanto maior for N, mais lento fica Evite usar paginação profunda
limit(N) Limitar o número de resultados Aumentar a eficiência Recomendado: ≤ 100

(1) limite: Limitar o número de resultados retornados

JAVASCRIPT
// === Back 10 items ===
db.products.find().limit(10);

// === Conditions for Cooperation ===
db.products.find({ category: "Electronics" }).limit(5);

// === limit(0) equivalent to limit(1) ===
db.products.find().limit(0);  // Back 1 items

// === limit(-1) Return All (Special)===
db.products.find().limit(-1);  // Back to All(Used as a reverse sort)

(2) pular Pular o documento

JAVASCRIPT
// === Skip to the beginning 10 items,Back to Page 11-20 items ===
db.products.find().skip(10).limit(10);

// === Page Numbering Formula ===
// Page N(per page 20 items):skip = (N - 1) * 20
db.products.find().skip((page - 1) * 20).limit(20);

// === skip + sort Consistency ===
db.products.find().sort({ _id: 1 }).skip(10).limit(10);

(3) ordenar

JAVASCRIPT
// === Ascending (1)===
db.products.find().sort({ price: 1 });     // Price (ascending)

// === Descending (-1)===
db.products.find().sort({ createdAt: -1 }); // Latest First

// === Sorting by Multiple Fields ===
db.products.find().sort({ category: 1, price: -1 });
// Press first category ascending,Press again price descending

// === Sorting Nested Fields ===
db.products.find().sort({ "specs.rating": -1 });

// === Sorting Array Fields ===
db.products.find().sort({ "tags.0": 1 });  // by tags Sorting the First Element

(4) Combinação de limite / pular / ordenar

JAVASCRIPT
// === Full Paginated Query ===
db.products
  .find({ category: "Electronics", isActive: true })
  .sort({ price: 1, createdAt: -1 })  // Price (ascending),Reverse Chronological Order
  .skip(20)                            // Skip 20 items
  .limit(10);                          // Back 10 items

// === mongoose Equivalent Notation ===
const products = await Product
  .find({ category: "Electronics", isActive: true })
  .sort({ price: 1, createdAt: -1 })
  .skip(20)
  .limit(10)
  .lean();

(5) Otimização do desempenho da paginação

Explicação do conceito: A paginação tradicional skip + limit sofre uma queda drástica no desempenho ao navegar em profundidade na paginação — skip(10000) exige a varredura de 10.000 documentos antes de descartá-los. A paginação baseada em cursor utiliza _id ou uma chave de classificação para localizar a posição inicial e salta diretamente para o documento de destino; portanto, seu desempenho não é afetado pela profundidade da paginação.

Análise comparativa:

Dimensão pular + limite Paginação do cursor
Desempenho em viradas de página profundas ❌ O(N) linear decrescente ✅ O(log N) estável
Suporte para pular de página ✅ Qualquer número de página ❌ Apenas navegação para frente e para trás
Contagem total Requer countDocuments Não é necessário
Casos de uso Gerenciamento de back-end (navegação entre páginas) Rolagem infinita, feed
100%
graph TB
    A[Pagination] --> B[skip + limit Traditional]
    A --> C[Cursor-Based Pagination<br/>Recommendations]

    B --> B1[skip(10000) Slow<br/>Scan 10000 items]
    C --> C1[lastId Search<br/>Direct Targeting]

    style C1 fill:#d4edda
JAVASCRIPT
// === Traditional Pagination(Slow page turns)===
const page1 = await Product.find().skip(0).limit(20);
const page1000 = await Product.find().skip(20000).limit(20);  // Slow!

// === Cursor-Based Pagination(Recommendations)===
const lastId = null;  // The First Time
const products1 = await Product.find({ _id: { $gt: lastId } }).limit(20);

const nextLastId = products1[products1.length - 1]._id;
const products2 = await Product.find({ _id: { $gt: nextLastId } }).limit(20);
// Stable performance,Not affected by page depth

▶ Exemplo 4: Paginação completa + ordenação

JAVASCRIPT
// === Comprehensive Practical Training:Pagination of Product List API ===
app.get('/api/products', async (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = Math.min(parseInt(req.query.limit) || 20, 100);
  const sortBy = req.query.sort || 'createdAt';
  const order = req.query.order === 'asc' ? 1 : -1;

  const products = await Product.find({ isActive: true })
    .select('sku title price thumbnail rating')
    .sort({ [sortBy]: order })
    .skip((page - 1) * limit)
    .limit(limit)
    .lean();

  const total = await Product.countDocuments({ isActive: true });

  res.json({
    data: products,
    pagination: {
      page,
      limit,
      total,
      pages: Math.ceil(total / limit),
      hasNext: page * limit < total,
      hasPrev: page > 1
    }
  });
});

8. contagem de documentos

Explicação do conceito: countDocuments e estimatedDocumentCount são dois métodos de contagem no MongoDB. O primeiro fornece uma contagem exata, mas requer a varredura dos documentos correspondentes, enquanto o segundo estima a contagem com base nos metadados da coleção — o que o torna extremamente rápido —, mas não suporta condições de filtro. Compreender a diferença entre os dois é fundamental para a paginação em páginas de lista e em cenários estatísticos.

Como funciona: countDocuments executa um plano de consulta para contar todos os documentos correspondentes; seu desempenho é diretamente proporcional ao número de correspondências. estimatedDocumentCount lê diretamente os metadados da coleção (contagem de documentos) sem executar nenhuma consulta; seu desempenho é O(1). Em grandes coleções de dados, a diferença de desempenho entre os dois pode ser superior a 100 vezes.

Dimensão countDocuments estimatedDocumentCount
Precisão ✅ Exata ⚠️ Estimada (erro < 5%)
Desempenho ⚠️ Lento (varredura completa da tabela) ⚡⚡ Extremamente rápido (O(1))
Critérios de filtragem ✅ Compatível ❌ Não compatível
Grande resumo ⚠️ Lento ⚡ Rápido
Em tempo real ✅ Em tempo real ⚠️ Quase em tempo real

(1) countDocuments: Contagem exata

JAVASCRIPT
// === Count all documents ===
db.products.countDocuments();
// 1250

// === Statistics Meet the Criteria ===
db.products.countDocuments({ category: "Electronics" });
// 250

// === With options ===
db.products.countDocuments(
  { category: "Electronics" },
  { limit: 1000 }   // Most Scans 1000 items
);

// === mongoose Equivalent ===
const count = await Product.countDocuments({ category: "Electronics" });
// 250

(2) estimatedDocumentCount: estimativa (mais rápida)

JAVASCRIPT
// === Estimated Total(Metadata-Based,Extremely fast)===
db.products.estimatedDocumentCount();
// 1250(Approximate value)

// === Applicable Scenarios ===
// - List Page Display"Total 1000 items"(No need for precision)
// - Statistics that do not require real-time processing

// === Performance Comparison ===
// countDocuments({}): ~100ms(Full Table Scan)
// estimatedDocumentCount(): ~1ms(Read Metadata)

(3) countDocuments vs estimatedDocumentCount

Dimensão countDocuments estimatedDocumentCount
Precisão ✅ Exata ⚠️ Estimada (erro < 5%)
Desempenho ⚠️ Lento (varredura completa da tabela) ⚡⚡ Extremamente rápido (O(1))
Critérios de filtragem ✅ Compatível ❌ Não compatível
Grande resumo ⚠️ Lento ⚡ Rápido
Em tempo real ✅ Em tempo real ⚠️ Quase em tempo real

▶ Exemplo 5: Uso prático de count

JAVASCRIPT
// === Product Category Statistics(Accurate)===
const stats = await Product.aggregate([
  { $group: { _id: "$category", count: { $sum: 1 } } },
  { $sort: { count: -1 } }
]);
// [
//   { _id: 'Electronics', count: 250 },
//   { _id: 'Books', count: 200 },
//   { _id: 'Clothing', count: 180 }
// ]

// === Total Number of List Pages(Estimate)===
const totalProducts = await Product.estimatedDocumentCount();
const electronicsCount = await Product.countDocuments({ category: "Electronics" });

res.json({
  total: totalProducts,        // Estimate 1250
  electronics: electronicsCount // Accurate 250
});

9. Processamento dos resultados da consulta

(1) Iteração do cursor

JAVASCRIPT
// === mongosh Iterate through the middle ===
db.products.find({ category: "Electronics" }).forEach(doc => {
  print(`SKU: ${doc.sku}, Title: ${doc.title}`);
});

// === Node.js Iterate through the middle ===
const cursor = collection.find({ category: "Electronics" });

// Methods 1:toArray()
const products = await cursor.toArray();

// Methods 2:for await...of
for await (const doc of collection.find({ category: "Electronics" })) {
  console.log(doc.title);
}

// Methods 3:Manual next()
const cursor2 = collection.find({ category: "Electronics" });
while (await cursor2.hasNext()) {
  const doc = await cursor2.next();
  console.log(doc);
}

(2) Configuração do cursor

JAVASCRIPT
// === Set the batch size ===
const cursor = collection.find({ category: "Electronics" })
  .batchSize(100);  // Per batch 100 items

// === Limit the Maximum Return ===
const cursor = collection.find({ category: "Electronics" })
  .limit(1000);

// === Limit Cursor Timeout ===
const cursor = collection.find({ category: "Electronics" })
  .maxTimeMS(5000);  // 5 Timeout in seconds

(3) Cadeias de consultas no Mongoose

JAVASCRIPT
// === Complete mongoose Query Chain ===
const products = await Product.find({ category: "Electronics" })
  .where('price').gt(100).lt(1000)        // Price 100-1000
  .where('stock').gt(0)                   // In stock
  .select('sku title price')              // Projection
  .sort({ price: 1 })                     // Sort
  .skip(20)                               // Pagination
  .limit(10)                              // Restrictions
  .populate('categoryId', 'name slug')    // Joined Queries
  .lean();                                // Performance Optimization

// === Equivalent, concise notation ===
const products2 = await Product.find({
  category: "Electronics",
  price: { $gt: 100, $lt: 1000 },
  stock: { $gt: 0 }
})
  .select('sku title price')
  .sort({ price: 1 })
  .skip(20)
  .limit(10)
  .lean();

▶ Exemplo 6: Guia prático para consultas compostas

JAVASCRIPT
// === Scene:E-commerce Product Search API ===
app.get('/api/products/search', async (req, res) => {
  const { q, category, minPrice, maxPrice, sortBy = 'relevance' } = req.query;

  // 1. Build a Query
  const query = { isActive: true };
  if (q) query.$text = { $search: q };
  if (category) query.category = category;
  if (minPrice || maxPrice) {
    query.price = {};
    if (minPrice) query.price.$gte = NumberDecimal(minPrice);
    if (maxPrice) query.price.$lte = NumberDecimal(maxPrice);
  }

  // 2. Sort
  const sortObj = sortBy === 'price_asc' ? { price: 1 } :
                  sortBy === 'price_desc' ? { price: -1 } :
                  sortBy === 'newest' ? { createdAt: -1 } :
                  { score: { $meta: 'textScore' } };  // Full-Text Search with Relevance Ranking

  // 3. Search
  const products = await Product.find(query, sortObj.score ? { score: { $meta: 'textScore' } } : {})
    .sort(sortObj)
    .limit(40)
    .lean();

  // 4. Statistics
  const total = await Product.countDocuments(query);

  res.json({
    query: { q, category, minPrice, maxPrice },
    total,
    products
  });
});

❓ Perguntas Frequentes

P: Qual é a diferença de desempenho entre find e findOne? R: find cria um Cursor (carregamento diferido), enquanto findOne retorna um Document diretamente. A diferença de desempenho é mínima (< 5%), mas findOne é mais simples. Use find para listas e findOne para detalhes.

P: O campo _id é retornado por padrão? R: Sim. _id é um campo incluído por padrão e deve ser explicitamente _id: 0 excluído. Caso contrário, mesmo que o _id seja omitido da projeção, ele ainda será retornado.

P: As projeções podem reduzir o tempo de consulta? R: Sim, mas apenas de forma limitada. O MongoDB precisa ler o documento inteiro para aplicar uma projeção (a menos que seja utilizada uma consulta coberta). As principais otimizações estão na transferência de dados pela rede e no tempo de desserialização; o impacto no tempo de execução da consulta é mínimo.

P: Por que o countDocuments é lento? R: Para obter uma contagem exata, é necessário fazer a varredura de todos os documentos correspondentes. Para coleções grandes, recomendamos usar o estimatedDocumentCount() (com base em metadados) ou retornar um valor estimado na API de paginação.

P: O skip fica mais lento quanto mais fundo você vai? R: Sim! O skip(N) precisa analisar os primeiros N documentos antes de retornar os resultados; portanto, quanto maior for o valor de N, mais lento ele fica. Para paginação profunda, recomendamos usar a paginação por cursor ({ _id: { $gt: lastId } }).

P: Preciso criar um índice no campo de classificação? R: É altamente recomendado. Caso contrário, o MongoDB terá que classificar na memória e, se o tamanho ultrapassar 32 MB, ocorrerá um erro Sort exceeded memory limit. Com um índice, a classificação tem complexidade O(log N).

P: O que significa find().limit(0)? R: Retorna 1 documento (um comportamento especial no MongoDB). Para retornar 0 documentos, use find({ _id: null }).

P: Qual é a finalidade de lean() no Mongoose? R: Ele ignora o processo de hidratação de documentos do Mongoose e retorna diretamente um objeto JavaScript simples. Isso proporciona um aumento de desempenho de 3 a 5 vezes, mas você perde o acesso aos métodos de documento do Mongoose (como save() e populate()). É adequado para cenários de consulta pura.


📖 Resumo


📝 Exercícios

  1. Questão básica (⭐): Insira 10 documentos de produtos no Mongosh, use find para consultar todos os documentos da categoria “Eletrônicos” e formate a saída usando pretty().

  2. Pergunta básica (⭐): Use findOne para consultar o produto com o SKU “PHONE-001” e utilize a projeção para retornar apenas os três campos: SKU, título e preço.

  3. Exercício avançado (⭐⭐): Escreva uma API em Node.js que implemente consultas paginadas para uma lista de produtos (usando os parâmetros page e limit), otimize o desempenho usando projection e lean() e retorne informações de paginação (total, pages e hasNext).

  4. Questão avançada (⭐⭐): Pesquise produtos com preços entre 100 e 1000, estoque > 0 e que pertençam às categorias “Eletrônicos” ou “Livros”; classifique-os em ordem crescente por preço e limite os resultados a 20.

  5. Problema avançado (⭐⭐): Compare os tempos de execução das consultas para skip(0).limit(20) e skip(10000).limit(20) em uma coleção de 1 milhão de documentos para compreender a questão da paginação profunda.

  6. Desafio (⭐⭐⭐): Implemente uma API de paginação baseada em cursor (usando lastId em vez de skip) que suporte paginação de qualquer profundidade sem comprometer o desempenho e inclua documentação completa da API e casos de teste.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%