018 — Normalización de 1FN a BCFN con dependencias funcionales
Parte 02 — Modelado conceptual y requisitos · Intermedio ·
4 horas estimadas · motores postgresql, sqlite · laboratorio
labs/01-sql-foundations · 4 fuentes.
Conceptos centrales: dependencia funcional · anomalía de actualización · BCFN · descomposición sin pérdida
En este caso se comparan 7 motores: 5 lo resuelven (5 con el resultado comprobado por máquina) y 2 no, con el motivo escrito.
De qué trata esta clase
La normalización explicada como lo que es: una demostración a partir de dependencias funcionales, no una intuición sobre qué pertenece a qué. Recorre de la primera forma normal a la de Boyce-Codd y exige que cada descomposición sea sin pérdida, es decir, que reunir las tablas devuelva exactamente la original.
flowchart LR
C["🗄️ Clase 018"]
C --> K1["dependencia funcional"]
C --> K2["anomalía de actualización"]
C --> K3["BCFN"]
C --> K4["descomposición sin pérdida"]
classDef raiz fill:#0b3d2e,stroke:#3fb950,color:#fff
class C raiz
Antes de empezar
Esta clase supone que ya trabajaste lo siguiente. Si algo de la última columna no te suena, vuelve a esa clase antes de seguir: aquí se usa sin volver a explicarlo.
| # | Clase previa | Lo que se da por sabido |
|---|---|---|
| 008 | Dos tablas y una relación: la clave foránea | clave foránea · tabla de relación · reunión · anomalías de repetición |
| 016 | Entidad-relación, cardinalidad y participación | entidad débil · cardinalidad · participación total · atributo de relación |
Vocabulario de la clase
Los términos que siguen se usan más adelante con este significado exacto. La definición completa, con sus términos relacionados, está en el glosario del programa.
Propósito
Normalizar con un criterio demostrable, no por costumbre. Las formas normales no son etiquetas de calidad: son teoremas sobre qué anomalías puede o no puede sufrir un esquema.
Resultados de aprendizaje
Al terminar podrás:
- Escribir dependencias funcionales a partir de reglas de negocio.
- Calcular el cierre de un conjunto de atributos y encontrar las claves candidatas.
- Justificar 1FN, 2FN, 3FN y BCFN señalando la anomalía que cada una elimina.
- Descomponer sin pérdida y comprobarlo.
- Explicar por qué BCFN puede no preservar dependencias, y qué hacer entonces.
Fundamentos
Dependencia funcional
X → Y significa: si dos filas coinciden en X, coinciden necesariamente en Y. Es una afirmación sobre todas las instancias posibles, no sobre los datos de hoy. Que hoy no haya dos estudiantes con el mismo nombre no permite escribir nombre → id.
Las dependencias vienen del dominio, no del dato. Por eso se descubren preguntando, no consultando.
Cierre y claves candidatas
El cierre X⁺ es el conjunto de atributos determinables desde X. Algoritmo:
X⁺ := X
repetir hasta que no cambie:
para cada dependencia A → B:
si A ⊆ X⁺ entonces X⁺ := X⁺ ∪ B
X es superclave si X⁺ incluye todos los atributos; es clave candidata si además ningún subconjunto propio lo consigue.
Las formas normales y su anomalía
| Forma | Exige | Anomalía que elimina |
|---|---|---|
| 1FN | Valores atómicos; sin grupos repetidos | No se puede consultar ni restringir lo que está dentro de un campo compuesto |
| 2FN | 1FN + ningún atributo no primo depende de parte de una clave | Redundancia en claves compuestas |
| 3FN | 2FN + ningún atributo no primo depende de otro no primo | Dependencia transitiva: actualizar en un sitio y no en otro |
| BCFN | Todo determinante es superclave | Redundancia residual con claves candidatas solapadas |
Las tres anomalías clásicas que aparecen si no se normaliza:
- De inserción: no se puede registrar un hecho porque falta otro no relacionado.
- De actualización: el mismo hecho está en N filas y se actualizan N−1.
- De eliminación: borrar una fila destruye un hecho independiente.
flowchart TD
U["Tabla universal"] --> A{"¿Valores atómicos?"}
A -- "No" --> A1["Separar → 1FN"]
A -- "Sí" --> B{"¿Dependencia parcial<br/>de una clave compuesta?"}
B -- "Sí" --> B1["Extraer el determinante → 2FN"]
B -- "No" --> C{"¿Dependencia transitiva<br/>entre no primos?"}
C -- "Sí" --> C1["Extraer el intermedio → 3FN"]
C -- "No" --> D{"¿Todo determinante<br/>es superclave?"}
D -- "No" --> D1["Descomponer → BCFN<br/>(puede perder dependencias)"]
D -- "Sí" --> E["BCFN"]
Descomposición sin pérdida
Descomponer R en R1 y R2 es sin pérdida si R1 ∩ R2 es superclave de al menos una de las dos. Si no, la reunión de las partes produce filas que no existían: se han inventado datos. Garcia-Molina, Ullman y Widom lo demuestran; en la práctica basta con comprobar la condición de la intersección antes de partir una tabla.
Ejemplo trabajado
Tabla sin normalizar:
inscripcion(student_id, course_id, student_nombre, course_nombre,
teacher_id, teacher_nombre, nota)
Dependencias del dominio:
D1 student_id -> student_nombre
D2 course_id -> course_nombre, teacher_id
D3 teacher_id -> teacher_nombre
D4 student_id, course_id -> nota
Clave candidata. Calculamos (student_id, course_id)⁺:
inicio {student_id, course_id}
por D1 + student_nombre
por D2 + course_nombre, teacher_id
por D3 + teacher_nombre
por D4 + nota
El cierre cubre todos los atributos, y ninguna de las dos columnas por separado lo consigue. Clave candidata: (student_id, course_id).
Diagnóstico:
- 2FN falla por D1 y D2:
student_nombredepende solo destudent_id, que es parte de la clave. Consecuencia medible: con 2 000 estudiantes y 8 inscripciones de media, el nombre de cada estudiante se almacena 8 veces. 16 000 copias para 2 000 hechos. - 3FN falla por D3:
teacher_nombredepende deteacher_id, que no es primo. Es transitiva.
Descomposición:
CREATE TABLE teachers (
id INTEGER PRIMARY KEY,
nombre TEXT NOT NULL
);
CREATE TABLE students (
id INTEGER PRIMARY KEY,
nombre TEXT NOT NULL
);
CREATE TABLE courses (
id INTEGER PRIMARY KEY,
nombre TEXT NOT NULL,
teacher_id INTEGER NOT NULL REFERENCES teachers(id)
);
CREATE TABLE enrollments (
student_id INTEGER NOT NULL REFERENCES students(id),
course_id INTEGER NOT NULL REFERENCES courses(id),
nota NUMERIC(2,1),
PRIMARY KEY (student_id, course_id)
);
Comprobación de no pérdida. enrollments ∩ students = {student_id}, que es clave de students. Se cumple. Igual para courses y teachers.
Las tres anomalías, ya resueltas:
| Anomalía | Antes | Después |
|---|---|---|
| Inserción | No se puede registrar un curso nuevo sin un estudiante inscrito | INSERT INTO courses basta |
| Actualización | Corregir el nombre de un profesor toca N filas | Toca 1 |
| Eliminación | Borrar la última inscripción borra el curso | El curso persiste |
El caso BCFN. Añadamos la regla «cada curso lo dicta un solo profesor, y cada profesor dicta un solo curso por período». Aparecen dos claves candidatas solapadas y una dependencia teacher_id, periodo → course_id cuyo determinante no es superclave de la tabla resultante. Descomponer para llegar a BCFN elimina la redundancia, pero la dependencia queda repartida entre dos tablas y ya no puede comprobarse sin reunirlas.
Ese es el compromiso real: BCFN no siempre preserva dependencias; 3FN siempre se puede alcanzar preservándolas. Cuando entran en conflicto, la decisión defendible suele ser quedarse en 3FN y auditar la dependencia con una invariante, en vez de perder la capacidad del motor de hacerla cumplir.
Comparación
| Nivel | Redundancia | Dependencias preservadas | Costo de consulta |
|---|---|---|---|
| Sin normalizar | Alta | — | Bajo (sin reuniones) |
| 3FN | Baja | Siempre alcanzable | Medio |
| BCFN | Mínima | No siempre | Medio-alto |
| Desnormalizado a propósito (clase 009) | Controlada y documentada | Vigiladas por invariantes | Bajo en lectura, alto en escritura |
Errores frecuentes
- Deducir dependencias de los datos actuales. Los datos muestran lo que ha ocurrido; la dependencia afirma lo que puede ocurrir.
- Normalizar hasta BCFN por reflejo. Si perder la dependencia obliga a comprobarla en la aplicación, puede ser peor remedio que enfermedad.
- Descomponer sin comprobar la no pérdida. Reunir dos partes mal elegidas fabrica filas que nunca existieron.
- Creer que 1FN prohíbe los tipos compuestos. Prohíbe los grupos repetidos y los valores no atómicos para el dominio; un
jsonbopaco que nunca se consulta por dentro es discutible, pero uno que se filtra por sus claves internas viola el espíritu de 1FN. - Confundir normalización con rendimiento. La normalización decide qué anomalías son posibles; el rendimiento se decide con índices y planes (parte 08).
De la clase a la operación
Los datos sucios que aparecen en los informes casi siempre son anomalías de actualización que nadie previno. Un esquema en 3FN convierte esos errores en imposibles por construcción, y eso vale más que cualquier proceso de limpieza posterior.
Reto de transferencia
- Toma una tabla ancha real de tu trabajo y escribe sus dependencias funcionales.
- Calcula el cierre y determina las claves candidatas.
- Diagnostica en qué forma normal está, nombrando la dependencia que la rompe.
- Descompón, comprueba la no pérdida y cuantifica cuántas copias redundantes eliminaste.
Preguntas de evaluación
- Da una dependencia funcional cierta en tu dominio que hoy los datos no reflejarían.
- Demuestra con un ejemplo pequeño que una descomposición con intersección no clave inventa filas.
- ¿Qué anomalía concreta elimina 3FN que 2FN no elimina?
- Presenta un caso donde te quedarías en 3FN pudiendo llegar a BCFN, y di quién vigila entonces la dependencia perdida.
🌐 El mismo problema en cada motor
Caso: Corregir el nombre de un profesor con una sola escritura
La prueba práctica de que un esquema está normalizado no es recitar formas normales: es que cada hecho está escrito una sola vez, así que corregirlo cuesta una escritura y no puede quedar a medias.
El caso parte del esquema descompuesto —profesores, cursos, inscripciones—
y corrige el nombre del profesor de DB-101 con un solo UPDATE. La consulta
devuelve, por curso, el nombre del profesor y cuántas inscripciones tiene.
En la tabla sin descomponer, ese nombre estaría repetido en cada
inscripción y bastaría olvidar una fila para que el mismo profesor tuviera
dos nombres distintos: la anomalía de actualización.
Salida esperada, idéntica en todos los motores que lo resuelven:
| curso | profesor | inscripciones |
|---|---|---|
DB-101 |
Ada Lovelace |
2 |
SE-201 |
Grace Hopper |
1 |
El contrato vive en motores.yaml y lo comprueba
python scripts/verificar_equivalencia.py --clase 018: 5 de
las 5 implementaciones se ejecutan de verdad y su
resultado se compara con esa tabla; el resto se declara como material revisado,
no ejecutado.
| Motor | ¿Resuelve el caso? | Nivel de prueba | Código | Fuente |
|---|---|---|---|---|
| SQLite | sí | núcleo | código | doc oficial |
| DuckDB | sí | núcleo | código | doc oficial |
| PostgreSQL | sí | servicio | código | doc oficial |
| MySQL | sí | servicio | código | doc oficial |
| MongoDB | sí | servicio | código | doc oficial |
| Apache Cassandra | no | — | — | doc oficial |
| Redis | no | — | — | doc oficial |
Los que resuelven el caso
SQLite · implementaciones/sqlite/consulta.sql
✅ verificado — se ejecuta en CI sin servicios
-- motor: sqlite
-- doc: https://sqlite.org/lang_update.html
-- nota: la prueba de la normalizacion esta en el UPDATE, no en el SELECT: una
-- sola escritura corrige el hecho en todas partes.
-- === preparacion ===
-- Forma normalizada: el nombre del profesor vive UNA vez.
CREATE TABLE profesores (
id INTEGER PRIMARY KEY,
nombre TEXT NOT NULL
);
CREATE TABLE cursos (
id INTEGER PRIMARY KEY,
codigo TEXT NOT NULL,
profesor_id INTEGER NOT NULL REFERENCES profesores(id)
);
CREATE TABLE inscripciones (
estudiante TEXT NOT NULL,
curso_id INTEGER NOT NULL REFERENCES cursos(id),
PRIMARY KEY (estudiante, curso_id)
);
INSERT INTO profesores (id, nombre) VALUES (1, 'A. Lovelace'), (2, 'Grace Hopper');
INSERT INTO cursos (id, codigo, profesor_id) VALUES (10, 'DB-101', 1), (20, 'SE-201', 2);
INSERT INTO inscripciones (estudiante, curso_id) VALUES
('Ada', 10), ('Linus', 10), ('Grace', 20);
-- La correccion de un dato es UNA escritura. En la tabla sin normalizar habria
-- que actualizar una fila por inscripcion, y bastaria olvidar una para que el
-- mismo profesor tuviera dos nombres.
UPDATE profesores SET nombre = 'Ada Lovelace' WHERE id = 1;
-- === consulta ===
SELECT c.codigo AS curso,
p.nombre AS profesor,
COUNT(i.estudiante) AS inscripciones
FROM cursos c
JOIN profesores p ON p.id = c.profesor_id
LEFT JOIN inscripciones i ON i.curso_id = c.id
GROUP BY c.id, c.codigo, p.nombre
ORDER BY c.codigo;
- Por qué sí: La descomposición y la reunión que la deshace son operaciones del modelo relacional puro: aquí se ven sin ruido de infraestructura.
- Por qué no: No tiene forma de declarar una dependencia funcional distinta de una clave, así que la normalización queda documentada en la cabeza de quien diseñó y no en el esquema.
- 📄 Documentación oficial: https://sqlite.org/lang_update.html
DuckDB · implementaciones/duckdb/consulta.sql
✅ verificado — se ejecuta en CI sin servicios
-- motor: duckdb
-- doc: https://duckdb.org/docs/stable/sql/statements/update.html
-- nota: para DESCUBRIR dependencias funcionales en datos existentes:
-- SELECT curso, COUNT(DISTINCT profesor) FROM plano GROUP BY curso
-- HAVING COUNT(DISTINCT profesor) > 1; -- si devuelve filas, la
-- dependencia esta rota y hay anomalias ya presentes.
-- === preparacion ===
-- Forma normalizada: el nombre del profesor vive UNA vez.
CREATE TABLE profesores (
id INTEGER PRIMARY KEY,
nombre VARCHAR NOT NULL
);
CREATE TABLE cursos (
id INTEGER PRIMARY KEY,
codigo VARCHAR NOT NULL,
profesor_id INTEGER NOT NULL
);
CREATE TABLE inscripciones (
estudiante VARCHAR NOT NULL,
curso_id INTEGER NOT NULL,
PRIMARY KEY (estudiante, curso_id)
);
INSERT INTO profesores (id, nombre) VALUES (1, 'A. Lovelace'), (2, 'Grace Hopper');
INSERT INTO cursos (id, codigo, profesor_id) VALUES (10, 'DB-101', 1), (20, 'SE-201', 2);
INSERT INTO inscripciones (estudiante, curso_id) VALUES
('Ada', 10), ('Linus', 10), ('Grace', 20);
-- La correccion de un dato es UNA escritura. En la tabla sin normalizar habria
-- que actualizar una fila por inscripcion, y bastaria olvidar una para que el
-- mismo profesor tuviera dos nombres.
UPDATE profesores SET nombre = 'Ada Lovelace' WHERE id = 1;
-- === consulta ===
SELECT c.codigo AS curso,
p.nombre AS profesor,
COUNT(i.estudiante) AS inscripciones
FROM cursos c
JOIN profesores p ON p.id = c.profesor_id
LEFT JOIN inscripciones i ON i.curso_id = c.id
GROUP BY c.id, c.codigo, p.nombre
ORDER BY c.codigo;
- Por qué sí: Es la herramienta ideal para el paso previo: descubrir las dependencias funcionales que hay de verdad en un volcado, contando cuántos valores distintos toma una columna por cada valor de otra.
- Por qué no: En analítica la normalización se revierte a propósito: el esquema en estrella repite el nombre del profesor en cada fila de hechos porque ahorrarse la reunión vale más que ahorrarse el espacio.
- 📄 Documentación oficial: https://duckdb.org/docs/stable/sql/statements/update.html
PostgreSQL · implementaciones/postgresql/consulta.sql
✅ verificado — se ejecuta contra el motor real levantado con docker compose
-- motor: postgresql
-- doc: https://www.postgresql.org/docs/current/ddl-constraints.html
-- nota: las claves foraneas hacen cumplir la descomposicion; sin ellas, la
-- normalizacion es solo una promesa del diagrama.
DROP TABLE IF EXISTS inscripciones, cursos, profesores;
-- === preparacion ===
-- Forma normalizada: el nombre del profesor vive UNA vez.
CREATE TABLE profesores (
id integer PRIMARY KEY,
nombre text NOT NULL
);
CREATE TABLE cursos (
id integer PRIMARY KEY,
codigo text NOT NULL,
profesor_id integer NOT NULL REFERENCES profesores(id)
);
CREATE TABLE inscripciones (
estudiante text NOT NULL,
curso_id integer NOT NULL REFERENCES cursos(id),
PRIMARY KEY (estudiante, curso_id)
);
INSERT INTO profesores (id, nombre) VALUES (1, 'A. Lovelace'), (2, 'Grace Hopper');
INSERT INTO cursos (id, codigo, profesor_id) VALUES (10, 'DB-101', 1), (20, 'SE-201', 2);
INSERT INTO inscripciones (estudiante, curso_id) VALUES
('Ada', 10), ('Linus', 10), ('Grace', 20);
-- La correccion de un dato es UNA escritura. En la tabla sin normalizar habria
-- que actualizar una fila por inscripcion, y bastaria olvidar una para que el
-- mismo profesor tuviera dos nombres.
UPDATE profesores SET nombre = 'Ada Lovelace' WHERE id = 1;
-- === consulta ===
SELECT c.codigo AS curso,
p.nombre AS profesor,
COUNT(i.estudiante) AS inscripciones
FROM cursos c
JOIN profesores p ON p.id = c.profesor_id
LEFT JOIN inscripciones i ON i.curso_id = c.id
GROUP BY c.id, c.codigo, p.nombre
ORDER BY c.codigo;
- Por qué sí: Las claves foráneas hacen cumplir la descomposición: no se puede insertar un curso cuyo profesor no exista, que es la mitad de lo que la normalización promete.
- Por qué no: Cada nivel de normalización añade una reunión a las consultas de lectura; en un panel que se abre mil veces por minuto, esas reuniones son el costo que después justifica desnormalizar a propósito.
- 📄 Documentación oficial: https://www.postgresql.org/docs/current/ddl-constraints.html
MySQL · implementaciones/mysql/consulta.sql
✅ verificado — se ejecuta contra el motor real levantado con docker compose
-- motor: mysql
-- doc: https://dev.mysql.com/doc/refman/8.4/en/group-by-handling.html
-- nota: con ONLY_FULL_GROUP_BY activo (por omision desde 5.7) esta consulta es
-- legal porque cada columna no agregada esta en el GROUP BY. Sin ese
-- modo, MySQL aceptaba consultas ambiguas y devolvia cualquier fila.
DROP TABLE IF EXISTS inscripciones;
DROP TABLE IF EXISTS cursos;
DROP TABLE IF EXISTS profesores;
-- === preparacion ===
-- Forma normalizada: el nombre del profesor vive UNA vez.
CREATE TABLE profesores (
id INT PRIMARY KEY,
nombre VARCHAR(50) NOT NULL
);
CREATE TABLE cursos (
id INT PRIMARY KEY,
codigo VARCHAR(50) NOT NULL,
profesor_id INT NOT NULL REFERENCES profesores(id)
);
CREATE TABLE inscripciones (
estudiante VARCHAR(50) NOT NULL,
curso_id INT NOT NULL REFERENCES cursos(id),
PRIMARY KEY (estudiante, curso_id)
);
INSERT INTO profesores (id, nombre) VALUES (1, 'A. Lovelace'), (2, 'Grace Hopper');
INSERT INTO cursos (id, codigo, profesor_id) VALUES (10, 'DB-101', 1), (20, 'SE-201', 2);
INSERT INTO inscripciones (estudiante, curso_id) VALUES
('Ada', 10), ('Linus', 10), ('Grace', 20);
-- La correccion de un dato es UNA escritura. En la tabla sin normalizar habria
-- que actualizar una fila por inscripcion, y bastaria olvidar una para que el
-- mismo profesor tuviera dos nombres.
UPDATE profesores SET nombre = 'Ada Lovelace' WHERE id = 1;
-- === consulta ===
SELECT c.codigo AS curso,
p.nombre AS profesor,
COUNT(i.estudiante) AS inscripciones
FROM cursos c
JOIN profesores p ON p.id = c.profesor_id
LEFT JOIN inscripciones i ON i.curso_id = c.id
GROUP BY c.id, c.codigo, p.nombre
ORDER BY c.codigo;
- Por qué sí: Mismo esquema y mismas garantías con InnoDB; es además el motor donde más se encuentran tablas heredadas sin normalizar, así que es donde más se practica la descomposición sobre datos reales.
- Por qué no: Su modo
ONLY_FULL_GROUP_BYno siempre estuvo activo: hay código antiguo que agrupa mal y sigue devolviendo un resultado, lo que esconde justo los errores que la normalización previene. - 📄 Documentación oficial: https://dev.mysql.com/doc/refman/8.4/en/group-by-handling.html
MongoDB · implementaciones/mongodb/consulta.js
✅ verificado — se ejecuta contra el motor real levantado con docker compose
// motor: mongodb
// doc: https://www.mongodb.com/docs/manual/data-modeling/concepts/embedding-vs-references/
// nota: modelo normalizado A PROPOSITO. Es lo correcto cuando el dato
// referenciado cambia y lo comparten muchos documentos; si el nombre del
// profesor estuviera incrustado en cada inscripcion, este updateOne
// tendria que ser un updateMany sobre miles de documentos.
// === preparacion ===
db.profesores.drop();
db.cursos.drop();
db.inscripciones.drop();
db.profesores.insertMany([
{ _id: 1, nombre: "A. Lovelace" },
{ _id: 2, nombre: "Grace Hopper" },
]);
db.cursos.insertMany([
{ _id: 10, codigo: "DB-101", profesor_id: 1 },
{ _id: 20, codigo: "SE-201", profesor_id: 2 },
]);
db.inscripciones.insertMany([
{ estudiante: "Ada", curso_id: 10 },
{ estudiante: "Linus", curso_id: 10 },
{ estudiante: "Grace", curso_id: 20 },
]);
db.profesores.updateOne({ _id: 1 }, { $set: { nombre: "Ada Lovelace" } });
// === consulta ===
db.cursos
.aggregate([
{ $lookup: { from: "profesores", localField: "profesor_id",
foreignField: "_id", as: "p" } },
{ $unwind: "$p" },
{ $lookup: { from: "inscripciones", localField: "_id",
foreignField: "curso_id", as: "i" } },
{ $project: { _id: 0, curso: "$codigo", profesor: "$p.nombre",
inscripciones: { $size: "$i" } } },
{ $sort: { curso: 1 } },
])
.forEach((d) => print(d.curso + "|" + d.profesor + "|" + d.inscripciones));
- Por qué sí: Admite el modelo normalizado con referencias entre colecciones, y es la forma correcta cuando el dato referenciado cambia y lo comparten muchos documentos, como el nombre de un profesor.
- Por qué no: Va contra la corriente del modelo documental: al no haber claves foráneas, nada impide que un curso apunte a un profesor borrado, y la reunión hay que pedirla explícitamente con
$lookupen cada consulta. - 📄 Documentación oficial: https://www.mongodb.com/docs/manual/data-modeling/concepts/embedding-vs-references/
Los que no resuelven este caso — y qué se hace en su lugar
Descartar un motor con un argumento es tan formativo como usarlo. Ninguna de estas filas dice que el motor sea peor: dice que este problema no es el suyo.
| Motor | Por qué no | Qué se hace en su lugar | Fuente |
|---|---|---|---|
| Apache Cassandra | Su guía de modelado recomienda lo contrario: duplicar el nombre del profesor en cada fila que lo necesite, porque no hay reuniones y una lectura debe resolverse en una sola partición. | Aceptar la duplicación y asumir el costo de actualizarla: cambiar el nombre del profesor pasa a ser un trabajo por lotes sobre todas las filas que lo copiaron, no un UPDATE. |
doc |
| Redis | No hay reuniones ni referencias que el servidor entienda: normalizar significaría hacer dos o tres viajes por lectura y reunir en el cliente. | Guardar el nombre del profesor en una clave propia y referenciarla desde la aplicación, aceptando que la coherencia entre claves la mantiene el código. | doc |
Laboratorio
python scripts/validate_repository.py
python labs/01-sql-foundations/run_lab.py
Guarda como evidencia la salida completa, la versión del motor y la semilla o los parámetros usados. Una captura sin comando no es evidencia: no se puede repetir.
Evaluación
| Criterio | Peso | Qué se comprueba |
|---|---|---|
| Comprensión conceptual | 25 % | Explica el mecanismo, no solo el resultado |
| Ejecución reproducible | 25 % | Otra persona obtiene lo mismo con las instrucciones dadas |
| Interpretación basada en evidencia | 25 % | Cada conclusión se apoya en una salida o una medición |
| Límites y riesgos declarados | 25 % | Dice qué no demuestra el ejercicio y qué faltaría en producción |
La clase se da por superada cuando la respuesta explica el mecanismo, muestra la salida que la respalda y declara al menos un límite del ejercicio.
Fuentes de esta clase
Todo lo afirmado arriba procede de estas obras. Los identificadores viven en
catalog/sources.json y el estado de los
enlaces se comprueba con python scripts/check_external_links.py.
- E. F. Codd (1970). A Relational Model of Data for Large Shared Data Banks. Communications of the ACM 13(6). DOI 10.1145/362384.362685.
Artículo fundacional del modelo relacional y de la independencia de datos. - Hector Garcia-Molina, Jeffrey D. Ullman, Jennifer Widom (2008). Database Systems: The Complete Book. 2.a ed. Pearson. ISBN 978-0-13-187325-4.
Tratamiento formal de dependencias funcionales, normalización y optimización. - Abraham Silberschatz, Henry F. Korth, S. Sudarshan (2019). Database System Concepts. 7.a ed. McGraw-Hill. ISBN 978-0-07-802215-9.
Texto de referencia universitario. El sitio oficial publica diapositivas y capítulos de muestra. - Raghu Ramakrishnan, Johannes Gehrke (2002). Database Management Systems. 3.a ed. McGraw-Hill. ISBN 978-0-07-246563-1.
Fuerte en álgebra relacional, evaluación de consultas y estructuras de almacenamiento.