Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/tutosrive/db-nosql-2026-1/llms.txt

Use this file to discover all available pages before exploring further.

Los talleres 4 y 5 del curso de Bases de Datos No Relacionales introducen el framework de agregaciones de MongoDB, la herramienta más poderosa del motor para transformar, agrupar y analizar datos a escala. Trabajamos sobre la colección personas, que contiene más de un millón de documentos generados con datos sintéticos en formato RandomUser.me, y construimos pipelines progresivamente más complejos: desde simples agrupaciones hasta combinaciones de $match, $group, $sort, $project y $limit en una sola consulta.

La Colección personas

Todos los ejercicios de los talleres 4 y 5 operan sobre la colección personas en la base de datos dbpersonas. Esta colección fue generada con el script Python del repositorio (ver Script Usuarios) y contiene documentos con la siguiente estructura, inspirada en la API RandomUser.me:
{
  "gender": "female",
  "name": {
    "title": "ms",
    "first": "ana",
    "last": "garcía"
  },
  "location": {
    "street": "123 calle principal",
    "city": "madrid",
    "state": "comunidad de madrid",
    "postcode": 28001,
    "coordinates": {
      "latitude": "40.4168",
      "longitude": "-3.7038"
    },
    "timezone": {
      "offset": "+1:00",
      "description": "Europe/Madrid"
    }
  },
  "email": "ana.garcia@example.com",
  "login": {
    "uuid": "a1b2c3d4-...",
    "username": "ana247",
    "password": "password123"
  },
  "dob": {
    "date": "1990-05-15T00:00:00Z",
    "age": 34
  },
  "registered": {
    "date": "2015-03-10T00:00:00Z",
    "age": 9
  },
  "nat": "ES"
}
Los campos más relevantes para los ejercicios de agregación son:
CampoDescripción
genderGénero: "male" o "female"
natCódigo de nacionalidad: US, ES, FR, BR, CA, DE, DK
location.cityCiudad de residencia (en minúsculas)
location.stateEstado o provincia (en minúsculas)
dob.ageEdad de la persona en años
registered.ageAntigüedad del registro en años

Ejercicios de Agrupación

Regla de oro: Cuando el enunciado dice “por cada” o “para cada” algo → usa $group. El campo que viene después de “por cada” es el _id del grupo.

Ejercicio 1 — Top 50 nacionalidades con más personas

Enunciado: Listar por nacionalidad de origen, el top de las 50 con más personas.
db.personas.aggregate([
  {
    $group: {
      _id: "$nat",
      total: { $sum: 1 }
    }
  },
  {
    $sort: { total: -1 }
  },
  { $limit: 50 }
])
Explicación por etapas:
EtapaQué hace
$groupAgrupa todos los documentos por el campo nat y cuenta cuántos hay por cada nacionalidad con $sum: 1
$sortOrdena los grupos de mayor a menor según el conteo (total: -1)
$limitDevuelve solo los primeros 50 resultados

Ejercicio 2 — Top 50 con proyección de campos

Enunciado: Mismo resultado que el ejercicio 1, pero renombrando los campos de salida: mostrar nacionalidad y conteo en lugar de _id y total.
db.personas.aggregate([
  {
    $group: {
      _id: "$nat",
      total: { $sum: 1 }
    }
  },
  {
    $sort: { total: -1 }
  },
  {
    $project: {
      _id: 0,
      nacionalidad: "$_id",
      conteo: "$total"
    }
  },
  { $limit: 50 }
])
Explicación por etapas:
EtapaQué hace
$groupIgual que el ejercicio 1: agrupa por nat y cuenta
$sortOrdena de mayor a menor por total
$projectSuprime _id (_id: 0), renombra _id del grupo a nacionalidad y total a conteo
$limitLimita a 50 documentos

Ejercicio 3 — Top 5 ciudades con menos personas

Enunciado: Top 5 de las ciudades donde viven las personas con menor cantidad de habitantes. Se presentan dos variantes: la básica (solo por total) y la completa (con ordenación doble por ciudad y total). Variante básica — solo ordena por total de menor a mayor:
db.personas.aggregate([
  {
    $group: {
      _id: "$location.city",
      total: { $sum: 1 }
    }
  },
  { $sort: { total: 1 } },
  { $limit: 5 }
])
Variante con ordenación doble — ordena primero por ciudad alfabéticamente de menor a mayor (_id: 1) y luego por total de menor a mayor (total: 1):
db.personas.aggregate([
  {
    $group: {
      _id: "$location.city",
      total: { $sum: 1 }
    }
  },
  { $sort: { _id: 1, total: 1 } },
  { $limit: 5 }
])
Explicación por etapas:
EtapaQué hace
$groupAgrupa por la ciudad (location.city) y cuenta personas por ciudad
$sortVariante básica: ordena solo por total de menor a mayor (total: 1). Variante doble: ordena por ciudad A→Z (_id: 1) y luego por total de menor a mayor (total: 1)
$limitRetorna solo los 5 primeros resultados

Ejercicio 4 — Conteo por género y nacionalidad

Enunciado: Número de personas ordenado por género y nacionalidad, con nacionalidad de menor a mayor.
db.personas.aggregate([
  {
    $group: {
      _id: {
        nacionalidad: "$nat",
        genero: "$gender"
      },
      totalPersonas: { $sum: 1 }
    }
  },
  {
    $sort: { "_id.nacionalidad": 1, genero: 1 }
  }
])
Explicación por etapas:
EtapaQué hace
$groupAgrupa usando un _id compuesto con dos campos: nat (como nacionalidad) y gender (como genero). Cuenta el total de personas por cada combinación
$sortOrdena por _id.nacionalidad de A→Z y luego por genero de A→Z
Cuando el _id del $group es un objeto compuesto, para referenciar sus campos en etapas siguientes usa la notación de punto: "_id.nacionalidad", "_id.genero".

Ejercicio 5 — Promedio de edad por género y nacionalidad (Top 5)

Enunciado: Promedio de las edades por género y nacionalidad → Top 5 de mayor a menor promedio.
db.personas.aggregate([
  {
    $group: {
      _id: {
        genero: "$gender",
        nacionalidad: "$nat"
      },
      promedio: { $avg: "$dob.age" }
    }
  },
  { $sort: { promedio: -1 } },
  {
    $project: {
      _id: 0,
      genero: "$_id.genero",
      nacionalidad: "$_id.nacionalidad",
      promedio: 1
    }
  },
  { $limit: 5 }
])
Explicación por etapas:
EtapaQué hace
$groupAgrupa por gender y nat, calcula el promedio de dob.age con $avg
$sortOrdena de mayor a menor por el campo promedio
$projectReestructura la salida: suprime _id, extrae genero y nacionalidad del objeto _id del grupo
$limitDevuelve solo los 5 primeros

Ejercicio 6 — Estadísticas de edad por género y ciudad

Enunciado: Filtrar personas con edad mayor a 20 años, agrupar por género y ciudad, y calcular promedio de edad, promedio de antigüedad de registro, edad máxima, edad mínima y total. Ordenar por promedio de antigüedad de registro de mayor a menor, limitar a 30.
db.personas.aggregate([
  { $match: { "dob.age": { $gt: 20 } } },
  {
    $group: {
      _id: { genero: "$gender", ciudad: "$location.city" },
      promedioEdadPersona:  { $avg: "$dob.age" },
      promedioEdadRegistro: { $avg: "$registered.age" },
      mayorEdad:            { $max: "$dob.age" },
      menorEdad:            { $min: "$dob.age" },
      totalLocal:           { $sum: 1 }
    }
  },
  { $sort: { promedioEdadRegistro: -1 } },
  { $limit: 30 }
])
Explicación por etapas:
EtapaQué hace
$matchFiltra primero los documentos donde dob.age > 20, antes de agrupar. Esto reduce la cantidad de documentos que procesan las etapas siguientes
$groupAgrupa por gender + location.city. Calcula: promedio de edad ($avg), promedio de edad de registro ($avg), edad máxima ($max), edad mínima ($min), y conteo total ($sum: 1)
$sortOrdena por promedioEdadRegistro de mayor a menor
$limitLimita la salida a 30 grupos

Ejercicio 7 — Total de personas por nacionalidad y estado (con filtro post-group)

Enunciado: Para cada nacionalidad y para cada estado, calcular el total de personas, pero mostrar solo los grupos con total mayor o igual a 10. Sin ordenación. Proyectar sin _id.
db.personas.aggregate([
  {
    $group: {
      _id: {
        nacionalidad: "$nat",
        estado: "$location.state"
      },
      total: { $sum: 1 }
    }
  },
  { $match: { total: { $gte: 10 } } },
  {
    $project: {
      _id: 0,
      nacionalidad: "$_id.nacionalidad",
      estado: "$_id.estado",
      total: 1
    }
  }
])
Explicación por etapas:
EtapaQué hace
$groupAgrupa por nat + location.state y cuenta el total de personas por cada combinación
$matchAplica un filtro sobre los grupos ya calculados: solo pasan los grupos donde total >= 10. Este $match viene después del $group, por eso filtra sobre campos calculados (como total)
$projectSuprime _id, expone nacionalidad, estado y total como campos de primer nivel

Contar por Género — 3 Formas

El ejercicio introductorio del taller propone contar personas por género de tres maneras distintas. Cada forma tiene su propio nivel de detalle y utilidad:

Forma 1 — Agrupación directa (básica)

El _id del grupo es directamente el valor del campo gender:
db.personas.aggregate([
  {
    $group: {
      _id: "$gender",
      total: { $sum: 1 }
    }
  }
])
Resultado: Dos documentos con _id: "male" y _id: "female".
Úsala cuando: Solo necesitas el conteo y no te importa el nombre del campo de salida.

Forma 2 — _id como objeto (explícita)

El _id del grupo es un objeto con un campo nombrado genero:
db.personas.aggregate([
  {
    $group: {
      _id: { genero: "$gender" },
      total: { $sum: 1 }
    }
  }
])
Resultado: _id: { genero: "male" } — el valor queda dentro de un objeto.
Úsala cuando: Vas a hacer una agrupación compuesta después (agregar más campos al _id) o quieres mantener la consistencia con otros pipelines.

Forma 3 — Con ordenación (básica + salida limpia)

Combina la agrupación con $sort para ordenar los resultados por conteo:
db.personas.aggregate([
  {
    $group: {
      _id: "$gender",
      total: { $sum: 1 }
    }
  },
  { $sort: { total: 1 } }
])
Resultado: _id: "female" y _id: "male" ordenados por total ascendente.
Úsala cuando: Necesitas el conteo ordenado sin preocuparte por el nombre del campo de salida.
También puedes combinarla con $project para obtener una salida sin _id y con nombres de campo personalizados:
db.personas.aggregate([
  {
    $group: {
      _id: "$gender",
      total: { $sum: 1 }
    }
  },
  {
    $project: {
      _id: 0,
      genero: "$_id",
      total: 1
    }
  },
  { $sort: { total: 1 } }
])
Resultado:
{ "genero": "female", "total": 25000000 }
{ "genero": "male",   "total": 25000000 }
Úsala cuando: El resultado va a ser consumido por una aplicación o necesitas una salida limpia sin _id.

Tips para Agregaciones

  • $match ANTES de $group: Filtra documentos crudos de la colección antes de agrupar. Siempre prefiere esta posición cuando puedas, porque reduce la cantidad de documentos que procesan las etapas posteriores y puede aprovechar índices de la colección.
    // ✅ Eficiente: filtra primero, agrupa menos documentos
    db.personas.aggregate([
      { $match: { "dob.age": { $gt: 20 } } },
      { $group: { _id: "$gender", total: { $sum: 1 } } }
    ])
    
  • $match DESPUÉS de $group: Filtra sobre los resultados calculados del grupo (campos como total, promedio, etc.), que no existen en los documentos originales. Úsalo cuando el filtro depende del resultado del $group.
    // ✅ Necesario aquí: filtra sobre 'total', que solo existe después de agrupar
    db.personas.aggregate([
      { $group: { _id: "$nat", total: { $sum: 1 } } },
      { $match: { total: { $gte: 10 } } }
    ])
    
Después de un $group, el resultado tiene la forma { _id: ..., campo1: ..., campo2: ... }. El $project sirve para:
  1. Eliminar _id del resultado (_id: 0) cuando no lo necesitas.
  2. Renombrar campos calculados a nombres más descriptivos (por ejemplo: totalconteo, _idnacionalidad).
  3. Seleccionar qué campos mostrar y cuáles omitir.
// Sin $project: resultado feo
{ "_id": "ES", "total": 7142857 }

// Con $project: resultado limpio
{ "nacionalidad": "ES", "conteo": 7142857 }
El $project no cambia los datos — solo transforma la presentación del documento de salida.
El orden de las etapas en un pipeline de agregación tiene impacto directo en el rendimiento:
PrincipioRecomendación
Filtrar tempranoColoca $match lo más al principio posible para reducir documentos
Aprovechar índicesUn $match al inicio puede usar índices de la colección; después de $group, no
Limitar antes de procesarSi sabes cuántos resultados necesitas, usa $limit lo antes posible
Proyectar campos innecesariosUsa $project para eliminar campos grandes que no necesitarás en etapas posteriores
MongoDB tiene un optimizador de pipelines que en algunos casos reordena etapas automáticamente (por ejemplo, mueve $match antes de $sort), pero es mejor no depender de esto y escribir pipelines ya optimizados.
Los acumuladores más comunes del operador $group:
AcumuladorDescripciónEjemplo
$sumSuma valores (con 1 cuenta documentos){ $sum: "$precio" } o { $sum: 1 }
$avgPromedio aritmético{ $avg: "$dob.age" }
$maxValor máximo del grupo{ $max: "$dob.age" }
$minValor mínimo del grupo{ $min: "$dob.age" }
$countCuenta documentos (MongoDB 5.0+){ $count: {} }
$pushConstruye un array con todos los valores{ $push: "$nombre" }
$addToSetArray de valores únicos (sin duplicados){ $addToSet: "$ciudad" }
$firstPrimer valor del grupo{ $first: "$nombre" }
$lastÚltimo valor del grupo{ $last: "$nombre" }

Build docs developers (and LLMs) love