DevTools

Cheatsheet MongoDB

Base de dados NoSQL orientada a documentos (JSON/BSON)

Volver a los lenguajes
MongoDB
78 tarjetas encontradas
Categorías:
Versiones:

Bases de Dados e Coleções


8 cards
Listar bases de datos
show dbs

// En el driver Node.js:
const dbs = await client.db().admin().listDatabases();
dbs.databases.forEach(d => console.log(d.name, d.sizeOnDisk));

show dbs lista todas las bases de datos en mongosh. Solo muestra bases con al menos un documento. Las bases vacías no aparecen.

Crear colección con opciones
// Colección simple
db.createCollection("clientes")

// Colección capped (tamaño fijo, tipo log)
db.createCollection("logs", {
  capped: true,
  size: 1048576,    // 1 MB
  max: 5000         // máx. documentos
})

createCollection() crea explícitamente. Las colecciones capped tienen tamaño fijo y sobrescriben las más antiguas — ideales para logs. La mayoría de las colecciones no necesita esto.

Seleccionar / crear base
use tienda

// La base se crea automáticamente
// al insertar el primer documento
db.productos.insertOne({ nombre: "Prueba" });

use nombre cambia a la base indicada. Si no existe, se crea implícitamente en el primer insert. No hay comando CREATE DATABASE explícito.

Eliminar colección
db.clientes.drop()

// También elimina todos los índices asociados
// Devuelve true si existía, false en caso contrario

// Verificar antes:
if (db.getCollectionNames().includes("temp")) {
  db.temp.drop();
}

drop() elimina la colección, sus documentos y todos los índices. Es irreversible. Para limpiar datos manteniendo la estructura, usa deleteMany({}).

Base de datos actual
db              // muestra el nombre de la base actual
db.getName()    // alternativa explícita

// Estadísticas
db.stats()
db.stats().objects  // total de documentos

db devuelve la referencia a la base actual. db.stats() muestra estadísticas como tamaño, número de documentos e índices.

Eliminar base de datos
// Elimina la base de datos actual
db.dropDatabase()

// Confirmación:
show dbs  // ya no aparece

db.dropDatabase() elimina toda la base actual — datos, colecciones e índices. Operación destructiva e irreversible. Confirma siempre con db.getName() antes.

Listar colecciones
show collections

// O programáticamente:
db.getCollectionNames()

// Con detalles:
db.getCollectionInfos()

show collections lista las colecciones de la base actual. getCollectionNames() devuelve un array. getCollectionInfos() incluye opciones como capped y validator.

Renombrar colección
db.clientesAntiguos.renameCollection("clientes")

// Con opción de sobrescribir destino:
db.temp.renameCollection("clientes", true)

renameCollection() cambia el nombre de la colección. El segundo argumento true permite sobrescribir una colección existente con el mismo nombre. Los índices se preservan.

Inserir Documentos


8 cards
insertOne
const resultado = db.clientes.insertOne({
  nombre: "Ana",
  edad: 30,
  ciudad: "Lisbon",
  activo: true
});

console.log(resultado.insertedId);

insertOne() inserta un único documento. Devuelve insertedId con el ObjectId generado. Si el campo _id no se indica, MongoDB lo crea automáticamente.

Arrays en documentos
db.clientes.insertOne({
  nombre: "Ray",
  tags: ["vip", "newsletter"],
  telefonos: [
    { tipo: "personal", numero: "912345678" },
    { tipo: "trabajo", numero: "213456789" }
  ]
});

Los campos pueden contener arrays de valores simples o de objetos. Los arrays de objetos son útiles para listas con metadata. Límite: un documento hasta 16 MB.

insertMany
const resultado = db.clientes.insertMany([
  { nombre: "Ray", edad: 25 },
  { nombre: "Mia", edad: 40 },
  { nombre: "Pepe", edad: 35 }
]);

console.log(resultado.insertedCount); // 3
console.log(resultado.insertedIds);

insertMany() inserta múltiples documentos en un array. insertedCount confirma cuántos se insertaron. Por defecto, se detiene en el primer error (ordered).

Tipos de datos
db.tipos.insertOne({
  texto: "string",
  entero: NumberInt(42),
  largo: NumberLong(9999999999),
  double: 3.14,
  booleano: true,
  fecha: new Date(),
  nulo: null,
  objectId: new ObjectId(),
  regex: /patron/i
});

BSON soporta String, Int32, Int64, Double, Boolean, Date, null, ObjectId, Array, Object y Regex.

insertMany (unordered)
db.clientes.insertMany(
  [
    { nombre: "A", _id: 1 },
    { nombre: "B", _id: 1 },  // ¡duplicado!
    { nombre: "C", _id: 3 }
  ],
  { ordered: false }
);
// Inserta A y C, ignora B

Con { ordered: false }, MongoDB sigue insertando el resto aunque uno falle. Más rápido para bulk inserts. Los errores quedan en writeErrors.

_id personalizado
// Usar un ID propio en vez de ObjectId
db.clientes.insertOne({
  _id: "ana@email.com",
  nombre: "Ana"
});

// O UUID
db.clientes.insertOne({
  _id: UUID(),
  nombre: "Ray"
});

El campo _id puede ser cualquier valor único (string, número, UUID). Si se omite, MongoDB genera un ObjectId de 12 bytes automáticamente.

Documentos anidados
db.clientes.insertOne({
  nombre: "Ana",
  direccion: {
    calle: "Av. Libertad",
    numero: 42,
    ciudad: "Lisbon",
    cp: "1000-001"
  }
});

MongoDB soporta objetos anidados (embedded documents). Ideal para datos accedidos en conjunto. Profundidad máxima: 100 niveles. Consulta con notación de punto.

Validación de schema
db.createCollection("clientes", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["nombre", "email"],
      properties: {
        nombre: { bsonType: "string" },
        edad: { bsonType: "int", minimum: 0 }
      }
    }
  }
});

$jsonSchema valida documentos en la inserción/update. Los campos en required son obligatorios. bsonType define el tipo esperado. Rechaza documentos inválidos.

Consultas (find)


8 cards
find (todos los documentos)
db.clientes.find()

// Con formato legible:
db.clientes.find().pretty()

// En el driver Node.js:
const docs = await db.collection("clientes").find({}).toArray();

find() sin filtro devuelve todos los documentos de la colección. Devuelve un cursor — los datos se cargan en lotes, no todos de una vez.

Ordenación (sort)
// Ascendente por edad
db.clientes.find().sort({ edad: 1 })

// Descendente por edad
db.clientes.find().sort({ edad: -1 })

// Múltiples campos
db.clientes.find().sort({ ciudad: 1, edad: -1 })

sort() ordena los resultados. 1 = ascendente, -1 = descendente. Puedes combinar campos. Sin índice, hace in-memory sort (límite 100 MB).

Filtro simple
// Igualdad exacta
db.clientes.find({ ciudad: "Lisbon" })

// Múltiples condiciones (AND implícito)
db.clientes.find({ ciudad: "Lisbon", activo: true })

// Por _id
db.clientes.find({ _id: ObjectId("65f...") })

Los filtros usan igualdad por defecto. Múltiples campos en el mismo objeto funcionan como AND. Para comparar por _id, usa ObjectId() como wrapper.

Limit y Skip (paginación)
// Primeros 10
db.clientes.find().limit(10)

// Página 3 (skip 20, limit 10)
db.clientes.find().sort({ nombre: 1 }).skip(20).limit(10)

// Orden recomendado: sort → skip → limit

limit() restringe el número de resultados. skip() salta documentos (paginación). Para datasets grandes, prefiere paginación por _id en vez de skip.

findOne
const cliente = db.clientes.findOne({ nombre: "Ana" });

// Devuelve el documento o null
if (cliente) {
  console.log(cliente.edad);
}

findOne() devuelve el primer documento que coincide (o null). No devuelve cursor — es directo. Útil cuando sabes que existe un solo resultado.

Contar documentos
// Total de la colección
db.clientes.countDocuments()

// Con filtro
db.clientes.countDocuments({ activo: true })

// Estimación rápida (usa metadata, no recorre)
db.clientes.estimatedDocumentCount()

countDocuments() cuenta con precisión (recorre documentos). estimatedDocumentCount() es instantáneo pero aproximado — usa metadata de la colección.

Proyección (campos)
// Incluir solo nombre y ciudad (excluir _id)
db.clientes.find({}, { nombre: 1, ciudad: 1, _id: 0 })

// Excluir campos específicos
db.clientes.find({}, { password: 0, notas: 0 })

// Campo anidado
db.clientes.find({}, { "direccion.ciudad": 1 })

El segundo argumento de find() es la proyección. 1 incluye, 0 excluye. El _id viene siempre por defecto — usa _id: 0 para omitirlo.

distinct
// Valores únicos de un campo
db.clientes.distinct("ciudad")
// ["Lisbon", "Porto", "Braga"]

// Con filtro
db.clientes.distinct("ciudad", { activo: true })

// En arrays, devuelve elementos únicos
db.clientes.distinct("tags")

distinct() devuelve un array con los valores únicos de un campo. Acepta un filtro como segundo argumento. En campos array, devuelve elementos individuales únicos.

Operadores de Consulta


8 cards
Comparación ($gt, $lt, $ne)
{ edad: { $gt: 18 } }    // mayor que
{ edad: { $gte: 18 } }   // mayor o igual
{ edad: { $lt: 65 } }    // menor que
{ edad: { $lte: 65 } }   // menor o igual
{ edad: { $ne: 30 } }    // distinto de
{ edad: { $eq: 30 } }    // igual a (explícito)

Operadores de comparación: $gt, $gte, $lt, $lte, $ne, $eq. Funcionan con números, strings y fechas.

$exists y $type
// El campo existe
{ telefono: { $exists: true } }

// El campo no existe
{ fax: { $exists: false } }

// Verificar tipo BSON
{ edad: { $type: "int" } }
{ fecha: { $type: "date" } }

$exists verifica la presencia del campo (aunque sea null). $type filtra por tipo BSON: "string", "int", "double", "bool", "date", "array", "object", "null".

$in y $nin
// La ciudad está en la lista
{ ciudad: { $in: ["Lisbon", "Porto", "Braga"] } }

// La ciudad NO está en la lista
{ ciudad: { $nin: ["Faro", "Evora"] } }

// Funciona con arrays: el campo contiene algún valor
{ tags: { $in: ["vip", "premium"] } }

$in coincide si el valor está en la lista. $nin es el inverso. En campos array, verifica si algún elemento coincide. Más eficiente que múltiples $or.

$regex (búsqueda de texto)
// Empieza con "Ana" (case-insensitive)
{ nombre: { $regex: "^Ana", $options: "i" } }

// Contiene "garcia"
{ nombre: { $regex: "garcia", $options: "i" } }

// Sintaxis alternativa
{ nombre: /^ana/i }

$regex permite patrones de expresión regular. $options: "i" ignora mayúsculas. Sin índice, hace collection scan. Los prefijos (^) pueden usar índice.

$and y $or
// OR: una de las condiciones
{ $or: [
  { edad: { $lt: 18 } },
  { vip: true }
]}

// AND explícito (necesario para el mismo campo)
{ $and: [
  { edad: { $gt: 18 } },
  { edad: { $lt: 65 } }
]}

$or exige al menos una condición verdadera. El $and explícito es necesario cuando aplicas dos operadores al mismo campo (ej.: un range con $gt y $lt).

Campos anidados y arrays
// Campo anidado (notación de punto)
{ "direccion.ciudad": "Porto" }

// El array contiene un valor
{ tags: "vip" }

// Array con una condición específica
{ notas: { $elemMatch: { $gt: 15, $lt: 20 } } }

// Tamaño del array
{ tags: { $size: 3 } }

La notación de punto accede a campos anidados. En arrays, la igualdad verifica si contiene el valor. $elemMatch aplica múltiples condiciones al mismo elemento. $size filtra por longitud.

$not y $nor
// NOT: invierte una condición
{ edad: { $not: { $gt: 65 } } }

// NOR: ninguna condición puede ser verdadera
{ $nor: [
  { ciudad: "Lisbon" },
  { vip: true }
]}

$not invierte un operador (incluye documentos sin el campo). $nor es lo opuesto de $or — ninguno puede coincidir. Cuidado: $not incluye docs donde el campo no existe.

$expr (expresiones)
// Comparar dos campos del mismo documento
{ $expr: { $gt: ["$gastos", "$presupuesto"] } }

// Con agregación dentro de find
{ $expr: {
  $and: [
    { $eq: ["$estado", "activo"] },
    { $gte: ["$saldo", 100] }
  ]
}}

$expr permite usar expresiones de aggregation dentro de find(). Útil para comparar campos entre sí. El prefijo $ referencia valores de campos.

Atualizar Documentos


8 cards
updateOne ($set)
db.clientes.updateOne(
  { nombre: "Ana" },
  { $set: { edad: 31, ciudad: "Porto" } }
);

// Resultado: { matchedCount: 1, modifiedCount: 1 }

updateOne() actualiza el primer documento que coincide con el filtro. $set define nuevos valores para los campos. Si el campo no existe, se crea.

$push y $pull (arrays)
// Añadir al array
db.clientes.updateOne(
  { _id: id },
  { $push: { tags: "premium" } }
);

// Quitar del array
db.clientes.updateOne(
  { _id: id },
  { $pull: { tags: "antiguo" } }
);

// Añadir varios
{ $push: { tags: { $each: ["a", "b"] } } }

$push añade un elemento al array (lo crea si no existe). $pull quita todos los elementos que coinciden. $each añade múltiples de una vez.

updateMany
db.clientes.updateMany(
  { ciudad: "Lisbon" },
  { $set: { region: "Centro-Sur" } }
);

// Resultado: { matchedCount: 150, modifiedCount: 150 }

updateMany() actualiza todos los documentos que coinciden con el filtro. Devuelve matchedCount y modifiedCount. Usa un filtro vacío {} para actualizar todos.

Upsert
db.clientes.updateOne(
  { email: "ana@mail.com" },
  {
    $set: { nombre: "Ana", visitas: 1 },
    $setOnInsert: { creadoEn: new Date() }
  },
  { upsert: true }
);

upsert: true crea el documento si ninguno coincide con el filtro. $setOnInsert solo se aplica en la creación (no en el update). Una combinación poderosa para "crear o actualizar".

$inc (incrementar)
// Sumar 10 a los puntos
db.clientes.updateOne(
  { _id: id },
  { $inc: { puntos: 10 } }
);

// Decrementar
db.clientes.updateOne(
  { _id: id },
  { $inc: { stock: -1 } }
);

$inc suma un valor al campo (lo crea con ese valor si no existe). Acepta negativos para decrementar. Operación atómica — segura en concurrencia.

replaceOne
db.clientes.replaceOne(
  { _id: id },
  { nombre: "Nuevo Nombre", edad: 25 }
);
// Sustituye TODO (excepto _id)
// ¡Los campos no incluidos se eliminan!

replaceOne() sustituye el documento entero por el nuevo objeto. Los campos no incluidos se pierden. Usa updateOne() con $set para updates parciales.

$unset (quitar campo)
// Quitar un campo
db.clientes.updateOne(
  { _id: id },
  { $unset: { fax: "" } }
);

// Quitar un campo anidado
db.clientes.updateOne(
  { _id: id },
  { $unset: { "direccion.piso": "" } }
);

$unset quita un campo del documento. El valor ("") es irrelevante. Funciona con campos anidados vía notación de punto. No elimina el documento.

$rename y $mul
// Renombrar campo
db.clientes.updateMany(
  {},
  { $rename: { "tel": "telefono" } }
);

// Multiplicar valor
db.productos.updateOne(
  { _id: id },
  { $mul: { precio: 1.1 } }  // +10%
);

$rename cambia el nombre de un campo en todos los documentos. $mul multiplica el valor numérico. Si el campo no existe, $mul lo crea con valor 0.

Eliminar Documentos


8 cards
deleteOne
const resultado = db.clientes.deleteOne({ nombre: "Ana" });
console.log(resultado.deletedCount); // 1

// Solo elimina el PRIMERO que coincide
// aunque varios tengan el nombre "Ana"

deleteOne() quita solo el primer documento que coincide con el filtro. Devuelve deletedCount. Para eliminar por _id, siempre es único.

Eliminar por _id
// Por ObjectId
db.clientes.deleteOne({ _id: ObjectId("65f3a...") });

// Múltiples IDs
db.clientes.deleteMany({
  _id: { $in: [ObjectId("..."), ObjectId("...")] }
});

Eliminar por _id es la forma más eficiente — usa el índice primario automáticamente. ObjectId() convierte la string. $in permite eliminar varios de una vez.

deleteMany
const resultado = db.clientes.deleteMany({ activo: false });
console.log(resultado.deletedCount); // N

// Con una condición compleja
db.clientes.deleteMany({
  $and: [
    { ultimoLogin: { $lt: new Date("2023-01-01") } },
    { plan: "free" }
  ]
});

deleteMany() quita todos los documentos que coinciden. Acepta filtros complejos con $and, $or, operadores de comparación, etc.

Bulk delete (bulkWrite)
db.clientes.bulkWrite([
  { deleteOne: { filter: { nombre: "A" } } },
  { deleteOne: { filter: { nombre: "B" } } },
  { deleteMany: { filter: { activo: false } } }
]);

bulkWrite() ejecuta múltiples operaciones (delete, insert, update) en una sola petición. Más eficiente que llamadas individuales. Reduce los round-trips al servidor.

Eliminar todos los documentos
// Quita todos (mantiene colección e índices)
db.clientes.deleteMany({});

// Alternativa más rápida (quita todo):
db.clientes.drop();
db.createCollection("clientes");
// ⚠️ ¡drop() también elimina índices!

deleteMany({}) limpia datos pero mantiene la colección y los índices. drop() es más rápido pero quita todo — tendrás que recrear los índices.

TTL (auto-expiración)
// Los documentos expiran tras 1 hora
db.sesiones.createIndex(
  { creadoEn: 1 },
  { expireAfterSeconds: 3600 }
);

// Insertar con una fecha
db.sesiones.insertOne({
  userId: "u1",
  creadoEn: new Date()
});

Los índices TTL (Time-To-Live) eliminan documentos automáticamente tras X segundos. MongoDB verifica cada 60s. Ideal para sesiones, tokens y datos temporales.

findOneAndDelete
const doc = db.clientes.findOneAndDelete(
  { email: "ana@mail.com" },
  { projection: { nombre: 1, email: 1 } }
);

// Devuelve el documento eliminado (o null)
console.log("Eliminado:", doc.nombre);

findOneAndDelete() quita y devuelve el documento en una operación atómica. Útil para colas de procesamiento. Acepta una projection para limitar los campos devueltos.

Cuidados al eliminar
// ❌ Peligroso: elimina TODO sin filtro
db.clientes.deleteMany()

// ✅ Siempre con un filtro específico
db.clientes.deleteMany({ _id: id })

// ✅ Verificar antes de eliminar
const count = db.clientes.countDocuments({ activo: false });
print(`Va a eliminar ${count} documentos`);

Confirma siempre el filtro antes de deleteMany(). Usa countDocuments() primero para verificar cuántos serán afectados. En producción, haz un backup antes de operaciones en masa.

Aggregation Pipeline


10 cards
Pipeline básico
db.clientes.aggregate([
  { $match: { activo: true } },
  { $sort: { edad: -1 } },
  { $limit: 5 },
  { $project: { nombre: 1, edad: 1, _id: 0 } }
]);

aggregate() procesa documentos en una secuencia de etapas (pipeline). Cada etapa transforma el resultado de la anterior. Orden típico: $match$sort$limit$project.

$lookup (join)
db.pedidos.aggregate([
  { $lookup: {
    from: "clientes",
    localField: "clienteId",
    foreignField: "_id",
    as: "cliente"
  }},
  { $unwind: "$cliente" }
]);

$lookup hace un left outer join con otra colección. from es la colección destino, localField/foreignField son las claves. El resultado es un array — usa $unwind para un objeto único.

$lookup (JOIN)
// "JOIN" entre collections:
db.pedidos.aggregate([
  {
    $lookup: {
      from: "clientes",        // collection
      localField: "cliente_id", // campo local
      foreignField: "_id",      // campo en la otra
      as: "cliente"             // nombre del resultado
    }
  },
  { $unwind: "$cliente" }  // opcional: 1 objeto
]);

// Cada pedido queda con el
// documento del cliente embebido

$lookup es el equivalente del JOIN: enlaza documentos de dos collections por localField = foreignField. El resultado queda en un array (as). Usa $unwind para convertirlo en un objeto único.

$group (agrupar)
db.clientes.aggregate([
  { $group: {
    _id: "$ciudad",
    total: { $sum: 1 },
    edadMedia: { $avg: "$edad" },
    masViejo: { $max: "$edad" }
  }}
]);

$group agrupa por _id (la expresión de agrupamiento). $sum: 1 cuenta. $avg, $min, $max calculan estadísticas. El prefijo $ referencia campos.

$addFields y $set
{ $addFields: {
  total: { $multiply: ["$precio", "$cantidad"] },
  iva: { $multiply: ["$precio", 0.23] },
  fechaFormateada: {
    $dateToString: { format: "%d/%m/%Y", date: "$creado" }
  }
}}

$addFields (alias $set) añade campos sin quitar los existentes. Útil para cálculos intermedios en el pipeline. $dateToString formatea fechas.

$unwind
// Documento con un array:
// { nombre: "Ana", tags: ["a", "b"] }

db.productos.aggregate([
  { $unwind: "$tags" }
]);

// Genera 1 documento por elemento:
// { nombre: "Ana", tags: "a" }
// { nombre: "Ana", tags: "b" }

// Con preserveNullAndEmptyArrays
// mantiene docs con array vacío:
{ $unwind: {
    path: "$tags",
    preserveNullAndEmptyArrays: true
} }

$unwind desnormaliza un array: crea un documento por cada elemento. Esencial antes de $group por valores de array. preserveNullAndEmptyArrays: true no descarta documentos sin el array.

$project (remodelar)
{ $project: {
  nombre: 1,
  esAdulto: { $gte: ["$edad", 18] },
  nombreCompleto: { $concat: ["$nombre", " ", "$apellido"] },
  anioNacimiento: { $subtract: [2025, "$edad"] }
}}

$project remodela documentos: incluye, excluye, renombra o calcula campos. Expresiones como $concat, $subtract, $gte crean campos derivados.

$bucket y $facet
// Agrupar en intervalos
{ $bucket: {
  groupBy: "$edad",
  boundaries: [0, 18, 35, 65, 120],
  default: "otro",
  output: { total: { $sum: 1 } }
}}

// Múltiples agregaciones en paralelo
{ $facet: {
  porCiudad: [{ $group: { _id: "$ciudad", n: { $sum: 1 } } }],
  porEdad: [{ $group: { _id: "$edad", n: { $sum: 1 } } }]
}}

$bucket agrupa en intervalos numéricos. $facet ejecuta múltiples pipelines en paralelo sobre los mismos datos — devuelve un objeto con cada resultado.

$unwind (desplegar arrays)
// Antes: { nombre: "Ana", tags: ["a", "b", "c"] }
// Después: 3 documentos, uno por tag

db.clientes.aggregate([
  { $unwind: "$tags" },
  { $group: { _id: "$tags", total: { $sum: 1 } } }
]);
// Cuenta ocurrencias de cada tag

$unwind crea un documento por cada elemento del array. Combinado con $group, cuenta frecuencias. preserveNullAndEmptyArrays: true mantiene los docs sin el array.

Acumuladores disponibles
// En $group:
$sum     // suma valores (o $sum: 1 para contar)
$avg     // media
$min     // mínimo
$max     // máximo
$first   // primer valor del grupo
$last    // último valor del grupo
$push    // array con todos los valores
$addToSet // array con valores únicos
$count   // conteo (MongoDB 5.0+)

Los acumuladores se usan dentro de $group. $push crea un array con todos los valores; $addToSet solo valores únicos. $count es un atajo para $sum: 1.

Índices


10 cards
Crear índice simple
// Índice ascendente
db.clientes.createIndex({ email: 1 });

// Índice descendente
db.clientes.createIndex({ creadoEn: -1 });

// Con un nombre personalizado
db.clientes.createIndex({ email: 1 }, { name: "idx_email" });

createIndex() crea un índice para acelerar consultas. 1 = ascendente, -1 = descendente. Sin índice, MongoDB hace collection scan (recorre todo).

Listar y eliminar índices
// Listar todos
db.clientes.getIndexes();

// Eliminar por nombre
db.clientes.dropIndex("email_1");

// Eliminar todos (excepto _id)
db.clientes.dropIndexes();

getIndexes() muestra el nombre, campos y opciones de cada índice. dropIndex() lo quita por nombre (por defecto: campo_orden). dropIndexes() quita todos excepto el _id.

Índice de texto
// Búsqueda full-text:
db.articulos.createIndex(
  { título: "text", cuerpo: "text" }
);

// Buscar:
db.articulos.find(
  { $text: { $search: "mongodb indices" } }
);

// Con score de relevancia:
db.articulos.find(
  { $text: { $search: "mongodb" } },
  { score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } });

// Solo 1 índice de texto
// por collection

Los índices text permiten búsqueda por palabras en varios campos. Usa $text + $search. $meta: "textScore" da la relevancia para ordenar. Límite: uno por collection.

Índice único
db.clientes.createIndex(
  { email: 1 },
  { unique: true }
);

// Permite múltiples null (sparse):
db.clientes.createIndex(
  { telefono: 1 },
  { unique: true, sparse: true }
);

unique: true impide valores duplicados (error code 11000). sparse: true ignora documentos sin el campo — permite múltiples null. Esencial para emails, usernames.

Índice parcial
// Solo indexa documentos activos
db.clientes.createIndex(
  { email: 1 },
  {
    unique: true,
    partialFilterExpression: { activo: true }
  }
);

partialFilterExpression crea un índice solo sobre documentos que cumplen la condición. Más pequeño y rápido que sparse. Útil cuando solo una fracción de los docs se consulta.

Índice TTL
// Elimina documentos automáticamente
// tras X segundos:
db.sesiones.createIndex(
  { creadaEn: 1 },
  { expireAfterSeconds: 3600 }  // 1 hora
);

// Los documentos con creadaEn más
// antigua que 1h se eliminan
// por un proceso en background

// Útil para: sesiones, logs,
// tokens, caches temporales

// El campo TIENE que ser Date

Los índices TTL (time-to-live) eliminan documentos automáticamente tras expireAfterSeconds. Ideal para sesiones, logs y tokens. El campo indexado tiene que ser Date; la limpieza corre en background.

Índice compuesto
db.clientes.createIndex({ ciudad: 1, edad: -1 });

// ¡El orden importa! Este índice sirve para:
db.clientes.find({ ciudad: "Lisbon" }).sort({ edad: -1 });

// Pero NO sirve para:
db.clientes.find().sort({ edad: -1 }); // sin ciudad

Los índices compuestos cubren múltiples campos. El orden sigue la regla ESR: Equality → Sort → Range. Las consultas deben usar el prefijo del índice para aprovecharlo.

explain (análisis de queries)
db.clientes.find({ ciudad: "Porto" })
  .explain("executionStats");

// Campos importantes:
// totalDocsExamined: 0 → usa índice ✅
// totalDocsExamined: 50000 → collection scan ❌
// executionTimeMillis: tiempo en ms
// indexName: qué índice se usó

explain("executionStats") muestra cómo se ejecuta la consulta. Si totalDocsExamined es mucho mayor que nReturned, falta un índice. Una herramienta esencial de optimización.

Índice de texto (text)
db.articulos.createIndex(
  { título: "text", contenido: "text" }
);

// Buscar
db.articulos.find(
  { $text: { $search: "mongodb tutorial" } },
  { score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } });

Los índices text permiten búsqueda full-text. $text + $search hace la consulta. $meta: "textScore" da relevancia. Máximo 1 índice text por colección.

Regla ESR (orden de índices)
// Consulta:
db.pedidos.find({ estado: "enviado", total: { $gt: 100 } })
  .sort({ fecha: -1 });

// Índice ideal (ESR):
db.pedidos.createIndex({
  estado: 1,   // Equality (primero)
  fecha: -1,   // Sort (segundo)
  total: 1     // Range (último)
});

Regla ESR: campos de igualdad primero, ordenación después, range al final. Maximiza el uso del índice. No siempre es posible seguirla al 100% — usa explain() para validar.

Dicas e Boas Práticas


10 cards
mongosh (CLI)
// Conectar
mongosh
mongosh "mongodb://localhost:27017/tienda"
mongosh "mongodb+srv://user:pass@cluster.mongodb.net/db"

// Ejecutar un fichero
mongosh script.js
mongosh --eval "db.clientes.countDocuments()"

mongosh es el shell moderno de MongoDB. Soporta JavaScript completo, autocomplete y --eval para comandos rápidos. Reemplaza el antiguo shell mongo.

Transacciones multi-documento
const session = client.startSession();
session.startTransaction();

try {
  await db.collection("cuentas").updateOne(
    { _id: "A" }, { $inc: { saldo: -50 } }, { session }
  );
  await db.collection("cuentas").updateOne(
    { _id: "B" }, { $inc: { saldo: 50 } }, { session }
  );
  await session.commitTransaction();
} catch (e) {
  await session.abortTransaction();
}

Las transacciones garantizan atomicidad en múltiples documentos/collections. Requieren un replica set o sharded cluster. Usa session en todas las operaciones. Más lentas que las operaciones simples.

Backup y restore
# Backup completo (binario):
mongodump --uri="mongodb://localhost:27017"

# Solo una base de datos:
mongodump --db=tienda --out=/backups

# Restore:
mongorestore --uri="mongodb://localhost:27017" /backups

# Exportar/importar JSON:
mongoexport --db=tienda --collection=clientes \
  --out=clientes.json
mongoimport --db=tienda --collection=clientes \
  --file=clientes.json

# mongodump = binario (rápido)
# mongoexport = JSON (legible)

mongodump/mongorestore hacen un backup binario (rápido y completo). mongoexport/mongoimport usan JSON (legible, para migraciones). Haz dumps regulares y prueba el restore.

ObjectId
// Generar nuevo
new ObjectId()

// Extraer fecha de creación
ObjectId("65f3a...").getTimestamp()
// ISODate("2024-03-14T...")

// Comparar
ObjectId("65f...").equals(otroId)

ObjectId tiene 12 bytes: timestamp (4) + máquina (3) + PID (2) + contador (3). getTimestamp() extrae la fecha de creación sin un campo extra. Ordenable cronológicamente.

Performance (consejos)
// ✅ Indexar campos de filtro y sort
db.c.createIndex({ campo: 1 });

// ✅ Proyección: buscar solo los campos necesarios
db.c.find({}, { nombre: 1, _id: 0 });

// ✅ limit() para no cargar todo
db.c.find().limit(20);

// ❌ Evitar regex sin índice
// ❌ Evitar skip() en datasets grandes
// ❌ Evitar documentos > 1 MB

Indexa campos de find() y sort(). Usa una proyección para reducir los datos transferidos. limit() evita cargar colecciones enteras. Monitoriza con explain().

Replica set y sharding
// Replica set = copias de los datos
// (alta disponibilidad):
//   1 primario + N secundarios
//   failover automático

// Iniciar replica set:
rs.initiate()
rs.status()
rs.add("host2:27017")

// Sharding = dividir datos por
// máquinas (escala horizontal):
//   la shard key define la división

sh.enableSharding("tienda")
sh.shardCollection(
  "tienda.pedidos", { cliente_id: 1 }
)

// Replica = disponibilidad
// Shard = capacidad/escala

Replica set replica datos en varios nodos (failover automático, lecturas en los secundarios). Sharding divide los datos por máquinas usando una shard key. Replica set para disponibilidad, sharding para escala horizontal.

Embed vs Reference
// EMBED (1:pocos, accedidos juntos)
{
  nombre: "Ana",
  direccion: { calle: "X", ciudad: "Porto" }
}

// REFERENCE (1:muchos, accedidos separados)
{
  nombre: "Ana",
  pedidos: [ObjectId("..."), ObjectId("...")]
}

Embed para datos pequeños y accedidos en conjunto (1:1, 1:pocos). Reference para relaciones grandes o accedidas separadamente (1:muchos). Límite: un documento hasta 16 MB.

Replica Set (conceptos)
// Iniciar replica set (3 nodos)
mongod --replSet rs0 --port 27017
mongod --replSet rs0 --port 27018
mongod --replSet rs0 --port 27019

// Inicializar
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "localhost:27017" },
    { _id: 1, host: "localhost:27018" },
    { _id: 2, host: "localhost:27019" }
  ]
});

Un Replica Set tiene un primary (escrituras) y secondaries (lecturas/failover). Garantiza alta disponibilidad. Necesario para transacciones y recomendado en producción.

Backup y restauración
// Backup completo
mongodump --db tienda --out /backup/

// Restauración
mongorestore --db tienda /backup/tienda

// Exportar/Importar (JSON)
mongoexport --db tienda --collection clientes --out clientes.json
mongoimport --db tienda --collection clientes --file clientes.json

mongodump/mongorestore hacen un backup binario (BSON). mongoexport/mongoimport usan JSON/CSV — útiles para migraciones y debugging. Programa backups regulares.

Comandos de administración
// Estado del servidor
db.serverStatus()

// Operaciones en curso
db.currentOp()

// Matar una operación lenta
db.killOp(opId)

// Profiling (registrar queries lentas)
db.setProfilingLevel(1, { slowms: 100 });
db.getProfilingLevel();

serverStatus() da métricas del servidor. currentOp() muestra las operaciones activas. setProfilingLevel(1) registra queries por encima de X ms en la colección system.profile.