Saltar al contenido

Clase 25 - NoSQL - MongoDB

#database #mongodb #mongosh

🧠 ¿Qué es MongoDB?

MongoDB es una base de datos NoSQL orientada a documentos que revoluciona la forma en que almacenamos y gestionamos datos. A diferencia de las bases de datos relacionales tradicionales (como MySQL o PostgreSQL), MongoDB almacena datos en formato JSON (específicamente BSON - Binary JSON).

✅ Características Clave

  • NoSQL: No usa tablas ni relaciones estrictas como las bases de datos relacionales.
  • Flexible: Permite esquemas dinámicos - puedes agregar campos sin modificar toda la estructura.
  • Escalable: Ideal para grandes volúmenes de datos con capacidades de sharding horizontal.
  • Orientado a documentos: Guarda la información como documentos JSON anidados.
  • Alto rendimiento: Optimizado para lecturas y escrituras rápidas (⚠️).
  • Replicación: Soporte nativo para alta disponibilidad.
  • Indexación: Sistema de índices flexible y potente.

🔄 MongoDB vs Bases de Datos Relacionales

ConceptoMongoDBSQL
Base de datosDatabaseDatabase
TablaCollectionTable
FilaDocumentRegister / Row
ColumnaFieldColumn
Clave primaria_idPrimary Key

⚙️ Levantando MongoDB con Docker

Vamos a levantar un contenedor con MongoDB para trabajar localmente de forma profesional.

🧪 Paso 1: Crear un docker-compose.yml

services:
mongodb:
image: mongo:7.0
container_name: mongodb_dev
ports:
- 27017:27017
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: password123
MONGO_INITDB_DATABASE: escuela
volumes:
- mongo_data:/data/db
restart: always
volumes:
mongo_data:

▶️ Paso 2: Ejecutar

Ventana de terminal
# Levantar los servicios
docker compose up -d
# Verificar que están corriendo
docker compose ps
# Ver logs si hay problemas
docker compose logs mongodb

MongoDB estará disponible en: mongodb://admin:password123@localhost:27017


🔌 Conectarse a MongoDB

Opciones de Conexión:

  1. MongoDB Compass (interfaz gráfica oficial)
  2. Mongo Shell (terminal)
  3. Mongo Express (interfaz web)

Usar Mongo Shell:

Ventana de terminal
# Conectar al contenedor
docker exec -it mongodb_dev mongosh

Comandos de Conexión y Navegación:

// Mostrar bases de datos
show dbs
// Mostrar base de datos actual
db
// Cambiar a una base de datos
use escuela
// Mostrar colecciones de la BD actual
show collections
// Obtener ayuda
help
// Ayuda específica para colecciones
db.help()
// Estadísticas de la base de datos
db.stats()
// Información del servidor
db.serverStatus()

📂 Crear y Gestionar Bases de Datos y Colecciones

Creación y Gestión de Bases de Datos:

// Crear o cambiar a una base de datos
use escuela
// La base de datos se crea cuando insertas el primer documento
// Eliminar base de datos actual
db.dropDatabase()
// Ver el tamaño de la base de datos
db.stats()

Creación y Gestión de Colecciones:

// Crear una colección explícitamente
db.createCollection("alumnos")
// Crear colección con opciones - "nuevo"
db.createCollection("profesores", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["nombre", "edad", "especialidad"],
properties: {
nombre: { bsonType: "string" },
edad: { bsonType: "int", minimum: 25, maximum: 70 },
especialidad: { bsonType: "string" }
}
}
}
})
// Listar colecciones
show collections
// Obtener información de una colección
db.alumnos.stats()
// Renombrar colección
db.alumnos.renameCollection("estudiantes")
// Eliminar colección
db.alumnos.drop()

📝 Insertar Documentos - Operaciones de Escritura

Insertar un solo documento:

// Inserción básica
db.alumnos.insertOne({
nombre: "Luis",
edad: 22,
email: "luis@gmail.com",
materias: ["Matemáticas", "Física", "Química"],
promedio: 8.5,
activo: true,
fechaIngreso: new Date(),
direccion: {
calle: "Av. Independencia 123",
ciudad: "Chubut",
codigoPostal: "4564"
}
})
// Con _id personalizado
db.alumnos.insertOne({
_id: "ALU001",
nombre: "Carlos",
edad: 24,
materias: ["Química", "Biología"],
promedio: 7.8
})

Insertar múltiples documentos:

db.alumnos.insertMany([
{
nombre: "Ana",
edad: 21,
email: "ana@gmail.com",
materias: ["Historia", "Literatura", "Filosofía"],
promedio: 9.2,
becario: true,
direccion: {
calle: "San Martín 456",
ciudad: "Córdoba",
codigoPostal: "5000"
}
},
{
nombre: "Diego",
edad: 23,
email: "diego@gmail.com",
materias: ["Programación", "Base de Datos", "Algoritmos"],
promedio: 8.7,
becario: false,
direccion: {
calle: "Belgrano 789",
ciudad: "Rosario",
codigoPostal: "2000"
}
},
{
nombre: "María",
edad: 20,
email: "maria@gmail.com",
materias: ["Arte", "Diseño", "Historia del Arte"],
promedio: 9.5,
becario: true
}
])
// Inserción con opciones
db.alumnos.insertMany([
{ nombre: "Pedro", edad: 25 },
{ nombre: "Facundo", edad: 26 }
], { ordered: false }) // Continúa insertando aunque falle uno

🔍 Consultas y Búsquedas - Operaciones de Lectura

Consultas Básicas:

// Mostrar todos los documentos
db.alumnos.find()
// Contar documentos
db.alumnos.countDocuments()
// Encontrar uno solo
db.alumnos.findOne()
// Buscar por campo específico
db.alumnos.find({ nombre: "Lucía" })
// Buscar con múltiples condiciones (AND implícito)
db.alumnos.find({
edad: 22,
activo: true
})

Operadores de Comparación:

// Mayor que ($gt)
db.alumnos.find({ edad: { $gt: 22 } })
// Mayor o igual que ($gte)
db.alumnos.find({ promedio: { $gte: 9.0 } })
// Menor que ($lt)
db.alumnos.find({ edad: { $lt: 23 } })
// Menor o igual que ($lte)
db.alumnos.find({ promedio: { $lte: 8.0 } })
// Diferente de ($ne)
db.alumnos.find({ activo: { $ne: false } })
// En un array de valores ($in)
db.alumnos.find({ edad: { $in: [20, 21, 22] } })
// No en un array de valores ($nin)
db.alumnos.find({ edad: { $nin: [25, 26, 27] } })
// Existe el campo ($exists)
db.alumnos.find({ email: { $exists: true } })

Operadores Lógicos:

// OR
db.alumnos.find({
$or: [
{ promedio: { $gt: 9 } },
{ becario: true }
]
})
// AND explícito
db.alumnos.find({
$and: [
{ edad: { $gte: 20 } },
{ edad: { $lte: 25 } }
]
})
// NOT
db.alumnos.find({
promedio: { $not: { $lt: 8.0 } }
})
// NOR (ni uno ni otro)
db.alumnos.find({
$nor: [
{ edad: { $lt: 20 } },
{ promedio: { $lt: 7.0 } }
]
})

Consultas en Arrays:

// Buscar documentos que tengan un elemento específico en un array
db.alumnos.find({ materias: "Matemáticas" })
// Buscar con múltiples elementos (todos deben estar presentes)
db.alumnos.find({ materias: { $all: ["Matemáticas", "Física"] } })
// Buscar por tamaño de array
db.alumnos.find({ materias: { $size: 3 } })
// Usar $elemMatch para condiciones complejas en arrays
db.alumnos.find({
"notas": {
$elemMatch: {
materia: "Matemáticas",
calificacion: { $gte: 8 }
}
}
})

Consultas en Documentos Anidados:

// Buscar en documento anidado usando notación de punto
db.alumnos.find({ "direccion.ciudad": "Chubut" })
// Múltiples campos en documento anidado
db.alumnos.find({
"direccion.ciudad": "Córdoba",
"direccion.codigoPostal": "5000"
})

Expresiones Regulares:

// Buscar nombres que contengan "Ana"
db.alumnos.find({ nombre: /Ana/ })
// Insensible a mayúsculas/minúsculas
db.alumnos.find({ nombre: /ana/i })
// Que empiece con "M"
db.alumnos.find({ nombre: /^M/ })
// Que termine con "ez"
db.alumnos.find({ nombre: /ez$/ })

Proyección - Seleccionar Campos:

// Mostrar solo nombre y edad (incluye _id por defecto)
db.alumnos.find({}, { nombre: 1, edad: 1 })
// Excluir _id
db.alumnos.find({}, { nombre: 1, edad: 1, _id: 0 })
// Excluir campos específicos
db.alumnos.find({}, { direccion: 0, fechaIngreso: 0 })
// Proyección en arrays (mostrar solo primer elemento)
db.alumnos.find({}, { "materias.$": 1 })

Ordenamiento y Limitación:

// Ordenar por edad ascendente
db.alumnos.find().sort({ edad: 1 })
// Ordenar por promedio descendente
db.alumnos.find().sort({ promedio: -1 })
// Ordenamiento múltiple
db.alumnos.find().sort({ promedio: -1, edad: 1 })
// Limitar resultados
db.alumnos.find().limit(5)
// Saltar documentos (paginación)
db.alumnos.find().skip(10).limit(5)
// Combinando todo
db.alumnos.find({ activo: true })
.sort({ promedio: -1 })
.limit(3)

🛠️ Actualizar Documentos - Operaciones de Modificación

Actualización de Un Documento:

// Actualizar un campo
db.alumnos.updateOne(
{ nombre: "Carlos" },
{ $set: { promedio: 8.3, activo: true } }
)
// Incrementar un valor numérico
db.alumnos.updateOne(
{ nombre: "Ana" },
{ $inc: { edad: 1 } }
)
// Agregar elemento a un array
db.alumnos.updateOne(
{ nombre: "Diego" },
{ $push: { materias: "Matemáticas Avanzadas" } }
)
// Agregar múltiples elementos a un array
db.alumnos.updateOne(
{ nombre: "María" },
{ $push: { materias: { $each: ["Escultura", "Pintura"] } } }
)
// Remover elemento de un array
db.alumnos.updateOne(
{ nombre: "Diego" },
{ $pull: { materias: "Algoritmos" } }
)
// Remover el último elemento de un array
db.alumnos.updateOne(
{ nombre: "Lucía" },
{ $pop: { materias: 1 } } // 1 para último, -1 para primero
)
// Eliminar un campo
db.alumnos.updateOne(
{ nombre: "Carlos" },
{ $unset: { fechaIngreso: "" } }
)
// Renombrar un campo
db.alumnos.updateOne(
{ nombre: "Ana" },
{ $rename: { "becario": "esBecario" } }
)

Actualización de Múltiples Documentos:

// Actualizar todos los documentos que coincidan
db.alumnos.updateMany(
{ edad: { $lt: 22 } },
{ $set: { categoria: "joven" } }
)
// Incrementar promedio para todos los becarios
db.alumnos.updateMany(
{ becario: true },
{ $inc: { promedio: 0.5 } }
)

Upsert (Actualizar o Insertar):

// Si no existe, lo crea
db.alumnos.updateOne(
{ nombre: "Roberto" },
{
$set: {
edad: 22,
promedio: 8.0,
materias: ["Introducción a la Programación"]
}
},
{ upsert: true }
)

Replace (Reemplazar Documento Completo):

db.alumnos.replaceOne(
{ nombre: "Carlos" },
{
nombre: "Carlos",
edad: 25,
email: "carlos.mendez.nuevo@email.com",
materias: ["Química Avanzada", "Biología Molecular"],
promedio: 8.8,
graduado: false
}
)

🗑️ Eliminar Documentos - Operaciones de Borrado

Eliminar Documentos:

// Eliminar un documento
db.alumnos.deleteOne({ nombre: "Ana" })
// Eliminar múltiples documentos
db.alumnos.deleteMany({ activo: false })
// Eliminar todos los documentos de la colección
db.alumnos.deleteMany({})
// Encontrar y eliminar (devuelve el documento eliminado)
db.alumnos.findOneAndDelete({ promedio: { $lt: 6.0 } })

📊 Agregaciones - Pipeline de Procesamiento

Las agregaciones son una de las características más poderosas de MongoDB para procesar y transformar datos.

Agregaciones Básicas:

// Promedio general de todos los alumnos
db.alumnos.aggregate([
{
$group: {
_id: null,
promedioGeneral: { $avg: "$promedio" },
totalAlumnos: { $sum: 1 },
promedioMaximo: { $max: "$promedio" },
promedioMinimo: { $min: "$promedio" }
}
}
])
// Agrupar por ciudad
db.alumnos.aggregate([
{
$group: {
_id: "$direccion.ciudad",
cantidadAlumnos: { $sum: 1 },
promedioEdad: { $avg: "$edad" }
}
}
])
// Estadísticas por rango de edad
db.alumnos.aggregate([
{
$group: {
_id: {
$switch: {
branches: [
{ case: { $lt: ["$edad", 21] }, then: "Menores de 21" },
{ case: { $lt: ["$edad", 25] }, then: "21-24 años" }
],
default: "25 años o más"
}
},
cantidad: { $sum: 1 },
promedioAcademico: { $avg: "$promedio" }
}
}
])

🔍 Índices - Optimización de Consultas

Los índices son cruciales para el rendimiento en colecciones grandes.

Crear y Gestionar Índices:

// Crear índice simple
db.alumnos.createIndex({ nombre: 1 }) // 1 = ascendente, -1 = descendente
// Crear índice compuesto
db.alumnos.createIndex({ edad: 1, promedio: -1 })
// Índice de texto para búsquedas
db.alumnos.createIndex({
nombre: "text",
"direccion.ciudad": "text"
})
// Índice en documento anidado
db.alumnos.createIndex({ "direccion.ciudad": 1 })
// Índice en array
db.alumnos.createIndex({ materias: 1 })
// Índice único
db.alumnos.createIndex({ email: 1 }, { unique: true })

Información de Índices:

// Listar todos los índices
db.alumnos.getIndexes()
// Eliminar índice
db.alumnos.dropIndex({ nombre: 1 })
// Eliminar todos los índices (excepto _id)
db.alumnos.dropIndexes()

🔄 Operaciones Bulk (Por Lotes)

Para operaciones eficientes con múltiples documentos:

// Bulk write ordenado
db.alumnos.bulkWrite([
{
insertOne: {
document: {
nombre: "Pedro Bulk",
edad: 25,
promedio: 8.0
}
}
},
{
updateOne: {
filter: { nombre: "Lucía García" },
update: { $set: { promedio: 9.0 } }
}
},
{
deleteOne: {
filter: { promedio: { $lt: 6.0 } }
}
}
], { ordered: true })
// Bulk write no ordenado (más eficiente)
db.alumnos.bulkWrite([
{
updateMany: {
filter: { edad: { $lt: 22 } },
update: { $set: { categoria: "junior" } }
}
},
{
updateMany: {
filter: { edad: { $gte: 22 } },
update: { $set: { categoria: "senior" } }
}
}
], { ordered: false })

🎯 Ejercicios Prácticos

📝 Ejercicio 1: Crear un Sistema de Gestión Académica

Crea una base de datos académica para gestionar la información de profesores.

Requisitos:

  1. Crear una colección llamada profesores con validación de esquema que incluya los siguientes campos requeridos:

    • nombre (string, mínimo 2 caracteres)

    • edad (entero, entre 25 y 70)

    • especialidad (string)

    • añosExperiencia (entero, mínimo 0)

  2. Insertar al menos tres documentos de ejemplo que contengan también los siguientes campos adicionales:

    • email

    • departamento

    • salario

    • activo (booleano)

  3. Realizar las siguientes consultas:

    • Buscar todos los profesores con más de 15 años de experiencia.

    • Calcular el promedio de edad, experiencia y salario de los profesores activos.

    • Generar estadísticas por departamento: número de profesores, experiencia total, salario promedio y listado de nombres.


📚 Ejercicio 2: Sistema de Cursos y Matriculaciones

Diseña una colección para administrar los cursos y su proceso de inscripción.

Requisitos:

  1. Crear una colección llamada cursos e insertar al menos dos documentos con los siguientes campos:

    • codigo (ej. "MAT101")

    • nombre

    • creditos

    • profesorId (relación con la colección profesores)

    • horario: un objeto con los días y el horario del curso.

    • cupoMaximo

    • inscritos: un arreglo inicialmente vacío.

  2. Matricular un alumno en un curso agregando al arreglo inscritos un objeto con:

    • alumnoId

    • fechaInscripcion (fecha actual)

    • estado (ej. "activo")

  3. Consultar los cursos con cupos disponibles, calculando el número de cupos restantes y mostrando solo aquellos con disponibilidad.