UD10 · Prácticas
RA7 Gestiona la información almacenada en bases de datos no relacionales, evaluando y utilizando las posibilidades que proporciona el sistema gestor. 5 CE
- RA7.a Se han caracterizado las bases de datos no relacionales.
- RA7.b Se han evaluado los principales tipos de bases de datos no relacionales.
- RA7.c Se han identificado los elementos utilizados en estas bases de datos.
- RA7.d Se han identificado distintas formas de gestión de la información según el tipo de base de datos no relacionales.
- RA7.e Se han utilizado las herramientas del sistema gestor para la gestión de la información almacenada.
RA1 Reconoce los elementos de las bases de datos analizando sus funciones y valorando la utilidad de los sistemas gestores. 1 CE
- RA1.b Se han identificado los distintos tipos de bases de datos según el modelo de datos utilizado.
| Práctica | Tipo | Nivel | CE principales |
|---|---|---|---|
| 10.1 Puesta en marcha de MongoDB | Guiada | ●○○ | RA7.c, RA7.e |
| 10.2 CRUD sobre los expedientes | Guiada | ●○○ | RA7.d, RA7.e |
| 10.3 Consultas con operadores, subdocumentos y arrays | Autónoma | ●●○ | RA7.d, RA7.e |
| 10.4 Agregaciones: el mismo informe en SQL y en MongoDB | Guiada | ●●○ | RA7.d, RA7.e |
| 10.5 Modelado documental de un catálogo | Autónoma | ●●● | RA7.c, RA7.d |
| 10.6 Otros modelos: clave-valor y grafos | Autónoma | ●●○ | RA7.a, RA7.b |
| 10.7 Reto: ¿relacional o NoSQL? | Reto | ●●● | RA7.a, RA7.b, RA1.b |
| Proyecto EduGest · UD10 | Proyecto | ●●● | RA7 completo |
Usaremos MongoDB Community Server 8.0 en Docker,
mongoshy MongoDB Compass. Los resultados indicados suponen que has cargado edugest_mongo.js y no has modificado los datos (repite la carga antes de cada práctica: el script borra y recrea las colecciones).
Práctica 10.1 · Puesta en marcha de MongoDB
Objetivo
Instalar el servidor, conectar con las dos herramientas cliente y reconocer los elementos de una base de datos documental.
Desarrollo
Arranca el servidor (si no lo hiciste en la guía del entorno):
docker run -d --name mongo8 -p 27017:27017 -v mongo-data:/data/db mongodb/mongodb-community-server:8.0-ubi9 docker exec -it mongo8 mongosh --eval "db.version()"Carga los datos de EduGest:
docker exec -i mongo8 mongosh < edugest_mongo.js # ciclos: 4 · profesores: 12 · expedientes: 32Explora con
mongosh:show dbs use edugest show collections db.expedientes.findOne({ _id: 1 }) db.expedientes.findOne({ _id: 30 }) db.ciclos.findOne({ _id: "SMR" }) db.expedientes.stats().size // tamaño en bytes db.expedientes.getIndexes()Explora con Compass. Conecta con
mongodb://localhost:27017. Abreedugest.expedientes, cambia entre las vistas Lista, JSON y Tabla y usa la pestaña Schema (Analyze) para ver qué campos tiene la colección y en qué porcentaje de documentos aparece cada uno.
Preguntas
- Compara los documentos con
_id1 y 30. ¿Qué campos tiene uno y no el otro? ¿Cómo se representaría esa diferencia en Oracle? - ¿Qué índices tiene la colección? ¿Quién los ha creado?
- Según la pestaña Schema de Compass, ¿en qué porcentaje de expedientes aparece el campo
dni? ¿Ygrupo? - ¿De qué tipo BSON es
fechaNacimiento? ¿Ynota?
Comprobación
db.expedientes.countDocuments()devuelve 32.Identificas que el único índice es el de
_id, creado automáticamente.dniaparece en el 87,5 % de los documentos (28 de 32) ygrupoen el 90,6 % (29 de 32).
Práctica 10.2 · CRUD sobre los expedientes
Objetivo
Realizar las cuatro operaciones básicas sobre documentos con subdocumentos y arrays, y comparar cada una con su equivalente SQL.
Desarrollo
Crear. Inserta a Marina López (consulta el ejemplo de la teoría) y a dos alumnos más con
insertMany. Uno de ellos sin_id: ¿qué valor le asigna MongoDB?Leer. Escribe y ejecuta:
db.expedientes.find({ "grupo.codigo": "1DAW" }, { _id: 0, nombre: 1, apellidos: 1 }).sort({ apellidos: 1 }) db.expedientes.countDocuments({ localidad: "Alicante" }) // 13 (sin contar los nuevos) db.expedientes.find({}, { nombre: 1, fechaNacimiento: 1 }).sort({ fechaNacimiento: 1 }).limit(3)Actualizar.
// a) Añadir un teléfono a quien no tiene (Paula, _id 4) db.expedientes.updateOne({ _id: 4 }, { $set: { "contacto.telefono": "612345678" } }) // b) Marcar a todo 2DAW como «en prácticas en empresa» db.expedientes.updateMany({ "grupo.codigo": "2DAW" }, { $set: { enFCT: true } }) // 5 // c) Corregir la nota de Bases de datos de Adrián (_id 1) con el operador posicional db.expedientes.updateOne( { _id: 1, "matriculas.modulo.codigo": "0484" }, { $set: { "matriculas.$.nota": 5 } }) // d) Añadir una falta a esa misma matrícula db.expedientes.updateOne( { _id: 1, "matriculas.modulo.codigo": "0484" }, { $push: { "matriculas.$.faltas": { fecha: ISODate("2026-05-20"), horas: 2, justificada: false } } }) // e) Quitar el campo enFCT de todos db.expedientes.updateMany({}, { $unset: { enFCT: "" } })Comprueba cada cambio con
findOney una proyección adecuada, por ejemplo:db.expedientes.findOne({ _id: 1 }, { "matriculas.modulo.codigo": 1, "matriculas.nota": 1 })Eliminar. Borra a los alumnos que has insertado en el paso 1 con una sola orden. Pista:
{ _id: { $in: [...] } }no sirve para el que no tenía_id. Usa el NIA.Equivalencias. Escribe, para cada orden de los pasos 2, 3a, 3b y 5, la sentencia SQL equivalente en EduGest-Oracle. ¿Cuál de las operaciones 3c y 3d necesita otra tabla en SQL?
Comprobación
El alumno insertado sin
_idrecibe unObjectId("...").3b informa
matchedCount: 5, modifiedCount: 5y 3emodifiedCount: 5.Tras el paso 3c, la matrícula de 0484 de Adrián tiene
nota: 5y el resto de sus matrículas no ha cambiado.Al final,
countDocuments()vuelve a ser 32.
Errores habituales
| Error | Causa |
|---|---|
MongoServerError: E11000 duplicate key error | Insertas un _id que ya existe |
Update document requires atomic operators | Has olvidado $set en un updateOne |
La actualización «no hace nada» (matchedCount: 0) | El filtro no coincide: revisa mayúsculas, tipos ("1" no es 1) y la notación de punto |
Práctica 10.3 · Consultas con operadores, subdocumentos y arrays
Enunciado
Resuelve cada consulta. Comprueba que obtienes el resultado indicado.
| # | Consulta | Resultado esperado |
|---|---|---|
| N1 | ¿Cuántos alumnos son de un grupo del ciclo DAM? | 13 |
| N2 | Nombre, apellidos y fecha de nacimiento del alumnado nacido antes de 2005, del mayor al menor | 10 documentos; el primero, Sara Amorós Guillem |
| N3 | Alumnado de Elche o de El Campello (usa $in) | 6 |
| N4 | _id del alumnado sin teléfono | 4, 10, 16, 22, 28 |
| N5 | Alumnado con alguna matrícula sin nota | 3, 8, 11, 17, 23, 28 |
| N6 | Alumnado con algún 10 | Aitana (_id 29) |
| N7 | Alumnado con más de 5 matrículas (usa $expr y $size) | Nerea, Hugo y Lucía |
| N8 | Alumnado cuyo primer apellido empieza por «Ib» (usa $regex) | 4 documentos |
| N9 | Profesorado que imparte algún módulo en 2DAM (colección profesores) | Javier, Lucía, Andrés y Raúl |
| N10 | Profesorado que no imparte clase | _id 108 a 112 |
| N11 | Ciclos con algún módulo de más de 200 horas | DAM, DAW y ASIR |
| N12 | Los tres alumnos más jóvenes | Manuel, Alba y Víctor |
Pista: la trampa de N5
Prueba primero db.expedientes.find({ "matriculas.nota": { $exists: false } }). Devuelve los _id 30, 31 y 32: los documentos en los que ningún elemento del array tiene nota (¡porque su array está vacío!). Para «algún elemento sin nota» necesitas $elemMatch:
db.expedientes.find({ matriculas: { $elemMatch: { nota: { $exists: false } } } }, { nombre: 1 })🔒 Solución de N7 y N9
Comprobación
Las doce consultas dan el resultado esperado.
Explicas con tus palabras la diferencia entre N5 con y sin
$elemMatch.Para N4 y N10 explicas qué equivalente tendrían en SQL (
IS NULL,NOT EXISTS/LEFT JOIN).
Práctica 10.4 · Agregaciones: el mismo informe en SQL y en MongoDB
Objetivo
Construir pipelines de agregación etapa a etapa y comparar su resultado y su legibilidad con la consulta SQL equivalente.
Desarrollo
Construye el pipeline en Compass. Abre
expedientes→ pestaña Aggregations. Añade las etapas una a una y observa la vista previa de cada una:[ { $group: { _id: "$localidad", alumnos: { $sum: 1 } } }, { $sort: { alumnos: -1, _id: 1 } } ]Debe coincidir con la práctica 7.1 (Alicante 13, Mutxamel 6…). Exporta el pipeline a código con el botón Export to language.
Media de Bases de datos por ciclo (
$unwind+$matchdespués del$unwind):db.expedientes.aggregate([ { $unwind: "$matriculas" }, { $match: { "matriculas.modulo.codigo": "0484" } }, { $group: { _id: "$matriculas.modulo.ciclo", calificadas: { $sum: { $cond: [ { $eq: [ { $type: "$matriculas.nota" }, "missing" ] }, 0, 1 ] } }, media: { $avg: "$matriculas.nota" } } }, { $project: { calificadas: 1, media: { $round: ["$media", 2] } } }, { $sort: { _id: 1 } } ])[ { _id: 'DAM', calificadas: 7, media: 5.93 }, { _id: 'DAW', calificadas: 6, media: 5.92 } ]Las cinco personas con más horas de falta (doble
$unwind):db.expedientes.aggregate([ { $unwind: "$matriculas" }, { $unwind: "$matriculas.faltas" }, { $group: { _id: "$_id", alumno: { $first: { $concat: ["$nombre", " ", "$apellidos"] } }, horas: { $sum: "$matriculas.faltas.horas" } } }, { $sort: { horas: -1, _id: 1 } }, { $limit: 5 } ])_id alumno horas 1 Adrián Ferri Baeza 8 10 Nerea Cerdá Tomás 8 9 Martina Alemany Vidal 7 14 Carla Valero Cerdá 6 7 Valeria Quiles Marco 5 Matrículas suspensas por grupo. Escríbelo tú. Resultado: 1ASIR 3, 1DAM 7, 1DAW 5, 2DAM 8, 2DAW 4.
Alumnado por nombre de ciclo con
$lookupsobre la colecciónciclos. Resultado: DAM 13, DAW 11, ASIR 5.Escribe en SQL (Oracle) las consultas de los pasos 2 a 5 y compara: número de líneas, legibilidad y qué te resulta más natural en cada caso.
Comprobación
- Los cinco pipelines dan los resultados indicados, y las consultas SQL dan los mismos.
- Sabes explicar por qué el
$matchdel paso 2 va después del$unwind. - En el paso 3 explicas por qué hace falta
$sortpor_idademás de porhoras(hay un empate en el quinto puesto).
$roundde MongoDB redondea los valores que están exactamente a mitad de camino hacia el número par (round half to even):$round: [2.345, 2]da 2.34. ElROUNDde Oracle redondea hacia arriba (2.35). Con los datos de EduGest no hay empates de este tipo, pero tenlo en cuenta al comparar informes.
Práctica 10.5 · Modelado documental de un catálogo
Objetivo
Diseñar una base de datos documental a partir de los patrones de consulta, justificando qué se incrusta y qué se referencia.
Contexto
Una tienda online de material informático quiere migrar su catálogo a MongoDB. Los productos son muy diferentes entre sí:
- Portátiles: procesador, RAM, almacenamiento, pantalla, peso.
- Ratones: conexión (USB/Bluetooth), DPI, si es inalámbrico.
- Monitores: pulgadas, resolución, frecuencia, paneles.
- Todos tienen: referencia, nombre, marca, precio, stock y una o varias categorías.
- Los clientes dejan opiniones (puntuación de 1 a 5, texto, fecha). Algunos productos tienen miles.
Consultas más frecuentes:
- Ficha del producto con sus características y las 5 opiniones más recientes.
- Listado por categoría con filtros por precio y marca.
- Puntuación media de cada producto.
Enunciado
- Diseña las colecciones y la estructura de los documentos. Justifica cada decisión de incrustar o referenciar en función de las consultas.
- Explica cómo resolverías la «media de puntuación» sin recalcularla en cada consulta (pista: patrón de campo calculado actualizado en cada nueva opinión).
- Crea la colección
productoscon un validador$jsonSchemaque exija los campos comunes,precio >= 0ystockentero no negativo. - Inserta al menos 10 productos de tres tipos distintos y 20 opiniones.
- Escribe las tres consultas frecuentes y crea los índices que las aceleran. Comprueba con
explain("executionStats")que usanIXSCAN. - Compara con un diseño relacional: ¿cuántas tablas harían falta para las características variables? (Piensa en el patrón entidad-atributo-valor o en una tabla por tipo de producto.)
Comprobación
- Las opiniones no están incrustadas sin límite en el producto (problema de los 16 MB); como mucho, las 5 últimas.
- El validador rechaza un producto con precio negativo.
- Las consultas usan índices.
Pista: patrón «subconjunto»
Guarda todas las opiniones en una colección opiniones (con la referencia del producto) y, además, incrusta en el producto un array ultimasOpiniones con las 5 más recientes. Al añadir una opinión: insértala en opiniones y actualiza el producto con $push + $each + $sort + $slice: 5, y $inc del número de opiniones y de la suma de puntuaciones.
Práctica 10.6 · Otros modelos: clave-valor y grafos
Objetivo
Experimentar brevemente con otros dos tipos de bases de datos NoSQL para evaluar sus puntos fuertes y sus limitaciones.
Parte A · Clave-valor con Valkey (compatible con Redis)
docker run -d --name valkey -p 6379:6379 valkey/valkey:8
docker exec -it valkey valkey-cliSET sesion:abc123 "id_alumno=1;rol=ALUMNO"
EXPIRE sesion:abc123 1800 # caduca en 30 minutos
TTL sesion:abc123
INCR visitas:portal # contador atómico
HSET alumno:1 nombre "Adrián" grupo "1DAM"
HGETALL alumno:1
KEYS alumno:* # ¡solo en pruebas! en producción usa SCANResponde: ¿cómo buscarías «todos los alumnos de 1DAM»? ¿Qué tendrías que haber guardado para poder hacerlo?
Parte B · Grafos con Neo4j
Crea un pequeño grafo de EduGest (5 alumnos, 3 módulos, 2 profesores) y resuelve con Cypher:
CREATE (a:Alumno {id:1, nombre:'Adrián'}), (b:Alumno {id:2, nombre:'Rubén'}),
(bd:Modulo {codigo:'0484'}), (pr:Modulo {codigo:'0485'}),
(marta:Profesor {nombre:'Marta'}), (lucia:Profesor {nombre:'Lucía'}),
(a)-[:MATRICULADO {nota:4.75}]->(bd), (b)-[:MATRICULADO {nota:4.75}]->(bd),
(a)-[:MATRICULADO {nota:7.25}]->(pr),
(marta)-[:IMPARTE]->(bd), (lucia)-[:IMPARTE]->(pr);
MATCH (a:Alumno {nombre:'Adrián'})-[:MATRICULADO]->(m)<-[:IMPARTE]-(p) RETURN m.codigo, p.nombre;
MATCH (a:Alumno)-[:MATRICULADO]->(m)<-[:MATRICULADO]-(c:Alumno) WHERE a.nombre = 'Adrián' RETURN DISTINCT c.nombre;Conclusión
Completa una tabla con los cuatro modelos (relacional, documental, clave-valor y grafos) y, para cada uno: estructura, lenguaje, una consulta en la que destaca y una consulta en la que sufre.
Práctica 10.7 · Reto: ¿relacional o NoSQL?
Enunciado
Para cada caso, recomienda el modelo (o la combinación de modelos) y justifícalo con argumentos de estructura de los datos, consultas, consistencia, escalabilidad y coste. Indica también qué perderías con la alternativa descartada.
- Secretaría virtual de la Conselleria: matrículas de todos los institutos de la Comunitat, con actas oficiales y certificados.
- App de apuntes colaborativos del alumnado: cada apunte tiene texto, imágenes, etiquetas variables, comentarios y versiones.
- Sensores de calidad del aire en todas las aulas del centro: CO₂, temperatura y humedad cada 30 segundos, consultados por intervalos de tiempo.
- Recomendador de ciclos formativos: «el alumnado que estudió lo mismo que tú y tiene tus intereses eligió…».
- Carrito de la compra de la tienda de la práctica 10.5.
Comprobación
Ninguna recomendación se basa en «es más moderno» o «es más rápido» sin explicar por qué.
Al menos un caso propone persistencia políglota (dos modelos a la vez) y explica cómo se sincronizan.
Usas la tabla de decisión de la teoría como punto de partida, no como regla automática.
Proyecto EduGest · UD10: el expediente documental
Enunciado
Exportación. Escribe una consulta en Oracle que genere, para cada alumno, su expediente en JSON usando las funciones SQL/JSON de Oracle (
JSON_OBJECT,JSON_ARRAYAGG). Exporta el resultado a un fichero e impórtalo en MongoDB (Compass → Add data → Import JSON, omongoimport).SELECT JSON_OBJECT( '_id' VALUE a.id_alumno, 'nia' VALUE a.nia, 'nombre' VALUE a.nombre, 'matriculas' VALUE ( SELECT JSON_ARRAYAGG(JSON_OBJECT('curso' VALUE m.curso_academico, 'modulo' VALUE mo.codigo, 'nota' VALUE m.nota_final ABSENT ON NULL)) FROM matricula m JOIN modulo mo ON mo.id_modulo = m.id_modulo WHERE m.id_alumno = a.id_alumno) ABSENT ON NULL RETURNING CLOB) AS expediente FROM alumno a;Validación. Añade a la colección un validador
$jsonSchemacoherente con las restricciones de Oracle (NIA de 8 dígitos, nota entre 0 y 10, convocatoria de 1 a 4).Consultas. Reescribe en MongoDB cinco de tus informes de la UD07 y compara resultados.
Índices. Crea los índices necesarios y muestra los planes (
explain).Informe final (
10-nosql.md): ventajas e inconvenientes del modelo documental para EduGest, qué partes del sistema tendrían sentido en MongoDB y cuáles deben seguir en Oracle, y cómo se mantendrían sincronizadas.
Comprobación
- Los 32 expedientes importados coinciden con los datos de Oracle.
- El validador rechaza una nota de 11.
- Las cinco consultas dan los mismos resultados en los dos sistemas.
- El informe final justifica las decisiones con criterios técnicos.
Oracle 23ai y 26ai incluyen JSON Relational Duality Views, que permiten consultar y modificar datos relacionales como si fueran documentos JSON, con todas las garantías de integridad del modelo relacional. Investiga esta característica: es un buen ejemplo de que la frontera entre SQL y NoSQL es cada vez más difusa.