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
- A principal diferença entre os métodos
findefindOne - Sintaxe básica dos filtros de consulta
- Projeção: Selecione o campo de retorno
- saída formatada com a função
pretty() - limitar / pular / ordenar: Paginação e ordenação
- countDocuments: Conta o número de documentos
- Como lidar com os resultados de consultas no Node.js / Mongoose
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:
// ❌ 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
// ✅ 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.
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
// === 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).
// === 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:
findO Cursor retornado não carrega todos os dados imediatamente, economizando memóriafindOneé, essencialmente,find().limit(-1); ele retorna o documento diretamente, eliminando a necessidade de criar um Cursor.- No Mongoose,
findretorna um arrayArray<T>, efindOneretorna um objetoT | null - Para determinar se um documento existe,
findOne+ verificação de valor nulo é mais eficiente do quefind+ 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
// === 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
// === 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.
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
// === 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 (...) |
⚠️ |
// === $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 |
// === $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
// === 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.
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.
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
// === 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
// === 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
// === 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
// === 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()
// === 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
# pretty Active in interactive mode,No differences in the script output
mongosh "mongodb://localhost:27017" --eval "db.products.find().pretty()"
(3) Formatação personalizada
// === 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.
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
// === 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
// === 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
// === 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
// === 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 |
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
// === 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
// === 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
// === 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)
// === 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
// === 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
// === 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
// === 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
// === 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
// === 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
findefindOne? R:findcria um Cursor (carregamento diferido), enquantofindOneretorna um Document diretamente. A diferença de desempenho é mínima (< 5%), masfindOneé mais simples. Usefindpara listas efindOnepara detalhes.
P: O campo _id é retornado por padrão? R: Sim.
_idé um campo incluído por padrão e deve ser explicitamente_id: 0excluí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 oestimatedDocumentCount()(com base em metadados) ou retornar um valor estimado na API de paginação.
P: O
skipfica mais lento quanto mais fundo você vai? R: Sim! Oskip(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, usefind({ _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 (comosave()epopulate()). É adequado para cenários de consulta pura.
📖 Resumo
findretorna um Cursor ao consultar vários documentos;findOneretorna um único Documento- Os filtros de consulta suportam mais de 30 operadores, incluindo operadores de comparação, lógicos, de elementos e de matrizes
- O controle “projeção” retorna um campo que pode reduzir o tamanho da resposta em 90% ou mais
- limit: Limitar o número de resultados exibidos; skip: Ignorar documentos; sort: Ordenar os resultados
- countDocuments: contagem exata; estimatedDocumentCount: estimativa rápida
- Use a paginação por cursor (com base em _id) para paginação profunda; não use
skip - As consultas encadeadas do Mongoose +
lean()oferecem o melhor desempenho
📝 Exercícios
-
Questão básica (⭐): Insira 10 documentos de produtos no Mongosh, use
findpara consultar todos os documentos da categoria “Eletrônicos” e formate a saída usandopretty(). -
Pergunta básica (⭐): Use
findOnepara 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. -
Exercício avançado (⭐⭐): Escreva uma API em Node.js que implemente consultas paginadas para uma lista de produtos (usando os parâmetros
pageelimit), otimize o desempenho usandoprojectionelean()e retorne informações de paginação (total,pagesehasNext). -
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.
-
Problema avançado (⭐⭐): Compare os tempos de execução das consultas para
skip(0).limit(20)eskip(10000).limit(20)em uma coleção de 1 milhão de documentos para compreender a questão da paginação profunda. -
Desafio (⭐⭐⭐): Implemente uma API de paginação baseada em cursor (usando
lastIdem vez deskip) que suporte paginação de qualquer profundidade sem comprometer o desempenho e inclua documentação completa da API e casos de teste.