En la lección anterior partías de un cliente que contaba lo que necesitaba y llegabas a un esquema. Aquí el punto de partida es el contrario: una tabla que ya existe y que está mal. Un listado plano exportado de una hoja de cálculo, un histórico con el nombre del socio copiado en cada fila, una tabla donde cambiar un teléfono obliga a tocar catorce registros. Tu trabajo es demostrar formalmente qué le pasa, predecir qué va a romper y arreglarla sin perder información.

Cómo trabajar esta lección. La normalización es de las pocas partes de las bases de datos que se puede hacer sin ordenador, con papel y lápiz, y conviene hacerlo así al menos las primeras veces. El procedimiento de cada ejercicio es siempre el mismo:

  1. Escribe las dependencias funcionales que se deducen de las reglas de negocio y de los datos.
  2. Calcula los cierres que necesites y determina todas las claves candidatas.
  3. Clasifica los atributos en primos y no primos.
  4. Recorre las formas normales en orden —1FN, 2FN, 3FN, FNBC, 4FN— y detente en la primera que se incumpla, señalando la dependencia culpable.
  5. Descompón, comprueba que la descomposición es sin pérdida y comprueba si conserva las dependencias.

Un aviso sobre los datos: en varios ejercicios verás una tabla con filas concretas. Los datos no demuestran una dependencia funcional, solo pueden refutarla. Que en cinco filas cada asesor aparezca con una única especialidad no prueba que asesor → especialidad; lo que lo prueba es la regla de negocio. En cambio, si dos filas tuvieran el mismo asesor con especialidades distintas, quedaría refutada. Los datos son la comprobación, las reglas son la fuente.

En esta lección no hay diseño desde requisitos (es 07-02) ni planes de ejecución (es 07-04). Solo análisis formal, descomposición y una decisión final de desnormalización.

Antes de Empezar

No hace falta ningún juego de datos previo: cada ejercicio trae el suyo y los del bloque C incluyen el SQL para crear la tabla de partida. Sí conviene tener a mano:

  • La definición de dependencia funcional y los axiomas de Armstrong (reflexividad, aumento, transitividad) de la lección 05-01.
  • El algoritmo de cierre de atributos X⁺: parte de X, y mientras haya alguna dependencia Y → Z con Y ⊆ X⁺, añade Z a X⁺.
  • Las definiciones de las formas normales de la lección 05-02, en la formulación que usaremos aquí:
Forma Se cumple cuando...
1FN Todos los atributos son atómicos y no hay grupos repetitivos ni listas
2FN Está en 1FN y ningún atributo no primo depende parcialmente de una clave candidata
3FN Está en 2FN y ningún atributo no primo depende transitivamente de una clave candidata
FNBC Para toda dependencia no trivial X → Y, X es superclave
4FN Está en FNBC y para toda dependencia multivaluada no trivial X ↠ Y, X es superclave

Recordatorio de vocabulario: un atributo es primo si pertenece a alguna clave candidata; no primo en caso contrario. Una dependencia es parcial si un atributo no primo depende de un subconjunto propio de una clave candidata; transitiva si va de la clave a un atributo no primo pasando por otro atributo no primo; y total o completa si depende de la clave entera y no de ninguna parte suya.

Si quieres ejecutar el SQL del bloque C, cualquier base PostgreSQL vacía sirve. Los INSERT ... SELECT DISTINCT funcionan igual en SQLite.

Contenido

  1. Bloque A — Básico: dependencias funcionales, cierres y claves candidatas (ejercicios 1-3)
  2. Bloque B — Intermedio: diagnóstico de tablas reales (ejercicios 4-6)
  3. Bloque C — Avanzado: normalización completa, FNBC, 4FN y desnormalización (ejercicios 7-10)
  4. Errores comunes y consejos
  5. Ejercicios de refuerzo

Bloque A — Básico: dependencias, cierres y claves

Ejercicio 1: Escribir las dependencias funcionales y calcular cierres

Dificultad: Básico

Enunciado. BiblioRed ofrece un servicio de reprografía. Cada trabajo de copia se registra en una única tabla con estos atributos:

copia_id, socio_id, socio_email, fecha, paginas, tipo_papel, tarifa_pagina, importe

Reglas de negocio:

  • Cada trabajo de copia tiene un identificador propio, copia_id.
  • Cada socio tiene un único correo electrónico y no hay dos socios con el mismo correo.
  • La tarifa por página depende exclusivamente del tipo de papel (normal, reciclado, satinado).
  • El importe es el resultado de multiplicar las páginas por la tarifa por página.

Se pide:

  • (a) Escribir el conjunto F de dependencias funcionales.
  • (b) Calcular {copia_id}⁺ paso a paso, indicando qué dependencia se usa en cada paso.
  • (c) Calcular {socio_email}⁺ y {tipo_papel, paginas}⁺.

Pista. «No hay dos socios con el mismo correo» es una dependencia funcional en la dirección que quizá no esperas.

Solución

(a) El conjunto F:

f1: copia_id             → socio_id, fecha, paginas, tipo_papel
f2: socio_id             → socio_email
f3: socio_email          → socio_id
f4: tipo_papel           → tarifa_pagina
f5: paginas, tarifa_pagina → importe

(b) Cierre de {copia_id} paso a paso:

Paso Dependencia aplicada X⁺ después del paso
0 — (inicio) {copia_id}
1 f1 (copia_id ⊆ X⁺) {copia_id, socio_id, fecha, paginas, tipo_papel}
2 f2 (socio_id ⊆ X⁺) + socio_email
3 f4 (tipo_papel ⊆ X⁺) + tarifa_pagina
4 f5 (paginas, tarifa_pagina ⊆ X⁺) + importe
5 Ninguna añade nada nuevo → fin Los 8 atributos

(c)

{socio_email}⁺        = {socio_email, socio_id}
{tipo_papel, paginas}⁺ = {tipo_papel, paginas, tarifa_pagina, importe}

Resultado esperado

Como {copia_id}⁺ contiene todos los atributos de la relación, copia_id es una superclave. Y como es un único atributo, es también mínima: es una clave candidata.

{socio_email}⁺ se queda en dos atributos: socio_email no es superclave, aunque sí determine a socio_id. {tipo_papel, paginas}⁺ se queda en cuatro: tampoco.

Explicación. Tres puntos que conviene fijar:

  • f3 es la dependencia que más gente olvida. «No hay dos socios con el mismo correo» significa que el correo determina al socio: socio_email → socio_id. Es una restricción UNIQUE traducida al lenguaje de las dependencias funcionales, y es lo que convierte al correo en una clave alternativa de la entidad socio. Ignorarla lleva a contar mal las claves candidatas en ejercicios posteriores.
  • f5 es una dependencia real, no una fórmula. La tentación es decir «el importe se calcula, no depende». Depende: dados unos valores concretos de paginas y tarifa_pagina, el importe está determinado. Que sea calculable no lo excluye del análisis; lo que ocurre es que en el diseño final probablemente lo resolvamos con una columna generada en lugar de con una tabla nueva.
  • El cierre se calcula hasta que deja de crecer, no hasta que has recorrido la lista una vez. En el paso 3 aparece tarifa_pagina, que es lo que en el paso 4 permite disparar f5. Si te hubieras parado tras una pasada, habrías concluido que importe no está en el cierre y copia_id no sería superclave.

Ejercicio 2: Determinar todas las claves candidatas y clasificar los atributos

Dificultad: Básico

Enunciado. La agenda de ocupación de salas de BiblioRed se ha exportado a una única tabla:

OCUPACION(sala, dia, hora, evento, aforo, sucursal)

Reglas de negocio:

  • Cada sala está en una única sucursal y tiene un único aforo.
  • En una sala, un día y una hora determinados solo puede celebrarse un evento.
  • Cada evento se celebra en un único sitio, un único día y a una única hora.

Se pide:

  • (a) Escribir F.
  • (b) Encontrar todas las claves candidatas, justificando que no hay más.
  • (c) Clasificar los atributos en primos y no primos.

Pista. Empieza por buscar los atributos que no aparecen en el lado derecho de ninguna dependencia: tienen que estar en todas las claves.

Solución

(a)

g1: sala             → aforo, sucursal
g2: sala, dia, hora  → evento
g3: evento           → sala, dia, hora

(b) Búsqueda sistemática de claves candidatas.

Primero, la clasificación de atributos según dónde aparecen:

Atributo ¿Aparece en algún lado izquierdo? ¿Aparece en algún lado derecho? Conclusión
sala Sí (g1, g2) Sí (g3) Puede o no estar en una clave
dia Sí (g2) Sí (g3) Puede o no
hora Sí (g2) Sí (g3) Puede o no
evento Sí (g3) Sí (g2) Puede o no
aforo No Sí (g1) Nunca está en una clave
sucursal No Sí (g1) Nunca está en una clave

Como ningún atributo queda fuera de todos los lados derechos, no hay un núcleo obligatorio y hay que probar combinaciones. Calculamos cierres:

{evento}⁺           : evento →(g3) sala, dia, hora →(g1) aforo, sucursal  = TODO  → superclave
{sala, dia, hora}⁺  : →(g2) evento →(g1) aforo, sucursal                 = TODO  → superclave
{sala}⁺             = {sala, aforo, sucursal}                            → no
{sala, dia}⁺        = {sala, dia, aforo, sucursal}                       → no
{dia, hora}⁺        = {dia, hora}                                        → no
{evento, sala}⁺     = TODO, pero contiene a {evento} → no es mínima

Claves candidatas: {evento} y {sala, dia, hora}.

{evento} es mínima por ser un único atributo. {sala, dia, hora} es mínima porque ninguno de sus tres subconjuntos propios de dos atributos es superclave ({sala,dia}, {sala,hora} y {dia,hora} se quedan cortos, como muestran los cierres). Cualquier otro conjunto que sea superclave contiene a una de las dos, luego no es mínimo.

(c)

  • Atributos primos (pertenecen a alguna clave candidata): evento, sala, dia, hora.
  • Atributos no primos: aforo, sucursal.

Resultado esperado

Concepto Valor
Claves candidatas {evento} y {sala, dia, hora}
Clave primaria (elección de diseño) {evento}, por ser más corta y estable
Primos evento, sala, dia, hora
No primos aforo, sucursal

Explicación. El método que conviene automatizar es este:

  1. Atributos que nunca aparecen a la derecha → están en todas las claves candidatas (aquí no hay ninguno).
  2. Atributos que nunca aparecen a la izquierda → no están en ninguna clave candidata (aquí, aforo y sucursal; por eso son no primos sin necesidad de más comprobaciones).
  3. Los demás hay que probarlos, empezando por los conjuntos pequeños.

El error frecuente es quedarse en la primera clave candidata que aparece. Es muy fácil ver {sala, dia, hora} —es la que sugiere la lectura del enunciado— y no darse cuenta de que {evento} también lo es, porque g3 es una dependencia que se lee «al revés» de como se piensa la agenda. Y esa segunda clave cambia el diagnóstico: aforo y sucursal dependen parcialmente de {sala, dia, hora} (porque sala es un subconjunto propio) y transitivamente de {evento}. La tabla ni siquiera está en 2FN.

Otro error: contar {evento, sala} como clave candidata porque su cierre es todo. Es superclave, no clave candidata: le sobra sala. La minimalidad es parte de la definición.


Ejercicio 3: Clasificar dependencias en parciales, transitivas y totales

Dificultad: Básico

Enunciado. Usando las dos relaciones de los ejercicios 1 y 2, clasifica cada una de estas dependencias respecto a la clave candidata que se indica. Las categorías son: total (o completa), parcial y transitiva. Indica además qué forma normal viola cada una.

# Relación Clave de referencia Dependencia
1 REPROGRAFIA {copia_id} copia_id → fecha
2 REPROGRAFIA {copia_id} tipo_papel → tarifa_pagina
3 REPROGRAFIA {copia_id} socio_id → socio_email
4 OCUPACION {sala, dia, hora} sala → aforo
5 OCUPACION {sala, dia, hora} sala, dia, hora → evento
6 OCUPACION {evento} sala → sucursal

Solución

# Clasificación Forma normal violada Razonamiento
1 Total Ninguna fecha depende de la clave entera, que es un único atributo. No hay subconjunto propio no vacío del que pudiera depender
2 Transitiva 3FN copia_id → tipo_papel → tarifa_pagina, y tipo_papel es no primo
3 Transitiva 3FN copia_id → socio_id → socio_email, con socio_id no primo
4 Parcial 2FN sala es subconjunto propio de la clave {sala, dia, hora} y aforo es no primo
5 Total Ninguna Es la propia clave determinando un atributo primo; ningún subconjunto propio de la clave determina evento
6 Transitiva 3FN Respecto a {evento}: evento → sala → sucursal. La misma dependencia es parcial respecto a la otra clave

Resultado esperado

REPROGRAFIA está en 2FN pero no en 3FN (dos dependencias transitivas: tipo_papel → tarifa_pagina y socio_id → socio_email).

OCUPACION está solo en 1FN: sala → aforo y sala → sucursal son parciales respecto a {sala, dia, hora}.

Explicación. El punto que hay que interiorizar está en las filas 4 y 6: la misma dependencia se clasifica de forma distinta según la clave que tomes como referencia. sala → sucursal es parcial respecto a {sala, dia, hora} y transitiva respecto a {evento}. No es una contradicción: son dos descripciones del mismo problema.

De ahí sale la regla operativa que ahorra tiempo: basta con que una dependencia viole una forma normal respecto a una clave candidata para que la relación no esté en esa forma normal. No hace falta que la viole respecto a todas. Por eso el orden correcto de trabajo es: encontrar todas las claves candidatas primero, y solo después evaluar las formas normales.

Segundo punto: REPROGRAFIA está en 2FN automáticamente, sin comprobar nada, porque su única clave candidata tiene un solo atributo. Una clave de un atributo no tiene subconjuntos propios no vacíos, luego no puede haber dependencias parciales. Toda relación con clave candidata única y simple está en 2FN por construcción. Comprobarlo a mano es tiempo perdido; reconocerlo es un atajo legítimo.

Tercer punto: la fila 5 recuerda que una dependencia cuyo lado derecho es un atributo primo nunca viola 2FN ni 3FN, porque las dos formas hablan explícitamente de atributos no primos. Sí podría violar FNBC, que no distingue.


Bloque B — Intermedio: diagnóstico de tablas reales

Ejercicio 4: Listado plano de inscripciones a eventos de BiblioRed

Dificultad: Intermedio

Enunciado. El área de actividades culturales trabaja con esta tabla, exportada de una hoja de cálculo. Cada fila es la inscripción de un socio a un evento.

evento_id titulo_evento fecha sala aforo_sala sucursal ponente ponente_email socio_id socio_nombre socio_email plazas
101 Club de lectura: Los pilares de la Tierra 2026-03-12 Sala Azul 30 Centro Rosa Calduch [email protected] 14 Marta Alsina [email protected] 2
101 Club de lectura: Los pilares de la Tierra 2026-03-12 Sala Azul 30 Centro Rosa Calduch [email protected] 11 Clara Ferrán [email protected] 1
101 Club de lectura: Los pilares de la Tierra 2026-03-12 Sala Azul 30 Centro Rosa Calduch [email protected] 13 Sonia Mestre [email protected] 3
103 Taller de escritura creativa 2026-05-09 Aula Sur 25 Sur Aitor Lemus [email protected] 14 Marta Alsina [email protected] 1
103 Taller de escritura creativa 2026-05-09 Aula Sur 25 Sur Aitor Lemus [email protected] 13 Sonia Mestre [email protected] 1
105 Club de lectura: Tokio blues 2026-07-16 Sala Este 50 Este Rosa Calduch [email protected] 15 Iván Pereda [email protected] 1

Reglas de negocio: cada evento tiene un único ponente principal, se celebra en una única sala y en una única fecha; cada sala pertenece a una sucursal y tiene un aforo; cada socio tiene un nombre y un correo único; cada ponente tiene un correo único.

Se pide:

  • (a) La clave candidata y las dependencias funcionales.
  • (b) En qué forma normal está y por qué, señalando la dependencia culpable.
  • (c) Predecir una anomalía concreta de modificación y otra de borrado, nombrando la fila y el dato exactos de la tabla anterior.

Solución

(a) Clave candidata: {evento_id, socio_id}. Una fila es «este socio, en este evento», y nada más corto identifica una fila.

h1: evento_id, socio_id → plazas
h2: evento_id           → titulo_evento, fecha, sala, ponente
h3: sala                → aforo_sala, sucursal
h4: ponente             → ponente_email
h5: socio_id            → socio_nombre, socio_email
h6: socio_email         → socio_id
h7: ponente_email       → ponente

Atributos primos: evento_id, socio_id, socio_email (porque {evento_id, socio_email} también es clave candidata, por h6). Todos los demás son no primos.

(b) La tabla está en 1FN y no en 2FN.

Las dependencias culpables son h2 y h5:

  • evento_id → titulo_evento es parcial: evento_id es un subconjunto propio de la clave {evento_id, socio_id} y titulo_evento es no primo. Lo mismo para fecha, sala y ponente.
  • socio_id → socio_nombre es parcial por el mismo motivo.

Como no llega a 2FN, no tiene sentido preguntarse por 3FN todavía. Pero conviene anotar que, una vez resuelta la 2FN, seguirían pendientes dos dependencias transitivas: h3 (evento_id → sala → aforo_sala, sucursal) y h4 (evento_id → ponente → ponente_email).

(c) Anomalías concretas.

Anomalía de modificación. Rosa Calduch cambia su correo a [email protected]. Su correo aparece en tres filas: las tres del evento 101 (con los socios 14, 11 y 13) y la del evento 105 (socio 15) — cuatro filas en total. Hay que actualizar las cuatro. Si el operador filtra por evento_id = 101 y actualiza solo esas tres, el resultado es una tabla donde la misma ponente tiene dos correos distintos: [email protected] en el evento 101 y [email protected] en el evento 105. La tabla queda internamente contradictoria y no hay ninguna restricción de la base de datos que lo impida, porque ponente_email no es clave de nada.

Anomalía de borrado. Iván Pereda (socio 15) cancela su inscripción al evento 105 y se borra la última fila. Con ella desaparece toda la información del evento 105: que se llamaba «Club de lectura: Tokio blues», que fue el 16 de julio de 2026, que se celebró en la Sala Este de la sucursal Este, que la sala tiene 50 plazas de aforo y que lo moderó Rosa Calduch. El evento existió, pero la base de datos ya no lo sabe. Y de propina, si esa era la única fila donde aparecía la Sala Este, también se pierde que su aforo es 50 y que pertenece a la sucursal Este.

Resultado esperado

Pregunta Respuesta
Forma normal 1FN (no llega a 2FN)
Dependencia culpable evento_id → titulo_evento, fecha, sala, ponente (parcial); también socio_id → socio_nombre, socio_email
Anomalía de modificación Cambiar el correo de Rosa Calduch exige tocar 4 filas; si se tocan 3, hay dos correos para la misma persona
Anomalía de inserción No se puede dar de alta el evento 107 («Cuentacuentos de otoño») hasta que alguien se inscriba
Anomalía de borrado Borrar la fila del socio 15 en el evento 105 hace desaparecer el evento entero

Explicación. El valor del ejercicio está en el apartado (c), y por eso el enunciado exige nombrar la fila y el dato exactos. Decir «hay redundancia» o «puede haber inconsistencias» no es un diagnóstico: es una descripción del olor. Un diagnóstico es «cambiar el correo de Rosa Calduch obliga a actualizar cuatro filas, y basta con que una se quede sin actualizar para que la tabla afirme dos cosas incompatibles».

Fíjate también en que la anomalía de inserción aparece por sí sola cuando intentas dar de alta algo que todavía no tiene hijos. El evento 107 está programado para octubre y aún no tiene inscritos: en esta tabla es inexpresable, salvo metiendo una fila con socio_id nulo, que rompería la clave primaria. Esa imposibilidad es la señal más clara de que dos entidades distintas se han metido en la misma tabla.

Un matiz de modelo que conviene ver: hemos asumido «un único ponente principal por evento». Si BiblioRed permitiera varios ponentes por evento —y de hecho lo permite: el evento 104 tiene dos—, h2 dejaría de ser cierta y la tabla ni siquiera estaría en 1FN de forma limpia, porque tendríamos un grupo repetitivo encubierto. Ese caso es exactamente el del ejercicio 9.


Ejercicio 5: Histórico de multas con datos del socio

Dificultad: Intermedio

Enunciado. El departamento de recaudación mantiene esta tabla para su informe mensual:

multa_id socio_id socio_nombre sucursal_id sucursal_nombre sucursal_telefono motivo importe estado
1 14 Marta Alsina 1 Centro 935550001 retraso 2.20 pagada
3 15 Iván Pereda 2 Norte 935550002 retraso 1.60 pendiente
4 14 Marta Alsina 1 Centro 935550001 perdida 24.00 pendiente
5 16 Nuria Bastos 3 Sur 935550003 deterioro 6.50 pagada
7 13 Sonia Mestre 1 Centro 935550001 retraso 5.00 pendiente

sucursal_id es la sucursal a la que está adscrito el socio.

Se pide: clave candidata, dependencias, forma normal y dependencia culpable, y una anomalía concreta de cada tipo con fila y dato exactos.

Pista. La clave es de un solo atributo. Eso descarta automáticamente un tipo de violación y obliga a buscar el otro.

Solución

Clave candidata: {multa_id}.

k1: multa_id    → socio_id, motivo, importe, estado
k2: socio_id    → socio_nombre, sucursal_id
k3: sucursal_id → sucursal_nombre, sucursal_telefono

Forma normal: 2FN, no 3FN.

Está en 2FN automáticamente: la clave candidata es un único atributo, luego no puede haber dependencias parciales.

No está en 3FN. Hay dos cadenas transitivas, y ambas son culpables:

  • multa_id → socio_id → socio_nombre (y → sucursal_id), con socio_id no primo.
  • multa_id → socio_id → sucursal_id → sucursal_nombre, sucursal_telefono, con sucursal_id no primo. Esta es una transitividad de dos saltos, y sigue siendo transitividad.

Anomalías concretas.

Modificación. La sucursal Centro cambia de teléfono al 935550099. El valor 935550001 aparece en tres filas: las multas 1, 4 y 7. Hay que actualizar las tres. Si un UPDATE con WHERE socio_id = 14 actualiza solo las multas 1 y 4, la multa 7 (de Sonia Mestre, también de Centro) sigue diciendo 935550001 y la tabla afirma que la sucursal Centro tiene dos teléfonos. Con 12.000 socios y cuatro sucursales, el número de filas a tocar por un solo cambio de teléfono es de varios miles.

Inserción. BiblioRed abre una quinta sucursal, «Poniente», teléfono 935550005. No hay ninguna forma de registrarla en esta tabla hasta que un socio adscrito a Poniente reciba una multa. Los datos de la sucursal solo existen como acompañamiento de una multa.

Borrado. Se anula la multa 5 (Nuria Bastos, deterioro, 6,50 €) y se borra la fila. Con ella desaparece que existe una sucursal Sur, que su teléfono es 935550003, que la socia 16 se llama Nuria Bastos y que está adscrita a Sur. Es la única fila de la tabla donde aparece esa sucursal.

Resultado esperado

Pregunta Respuesta
Clave candidata {multa_id}
Forma normal 2FN (no 3FN)
Dependencias culpables socio_id → socio_nombre, sucursal_id y sucursal_id → sucursal_nombre, sucursal_telefono
Fila y dato de la anomalía de modificación El teléfono 935550001 en las multas 1, 4 y 7
Fila y dato de la anomalía de borrado Borrar la multa 5 elimina la existencia de la sucursal Sur y de la socia 16

Descomposición a 3FN (solo el resultado; el procedimiento completo está en el ejercicio 7):

SUCURSALES(sucursal_id, sucursal_nombre, sucursal_telefono) SOCIOS(socio_id, socio_nombre, sucursal_id) MULTAS(multa_id, socio_id, motivo, importe, estado)

Explicación. Este es el caso de manual de violación de 3FN, y merece la pena ver por qué el diagnóstico correcto es 2FN y no «1FN» ni «3FN»:

  • No es 1FN el diagnóstico porque todos los atributos son atómicos: no hay listas, ni grupos repetitivos, ni celdas con varios valores.
  • No llega a 3FN porque hay atributos no primos que dependen de otros atributos no primos.
  • El atajo de la pista: con clave candidata simple, la 2FN está garantizada y toda la atención debe ir a buscar cadenas clave → X → Y.

Un detalle que se pasa por alto: la transitividad encadenada. sucursal_telefono está a dos saltos de la clave (multa_id → socio_id → sucursal_id → sucursal_telefono). Sigue siendo una violación de 3FN, y la descomposición correcta no es meter la sucursal dentro de la tabla de multas: es reconocer que hay tres entidades escondidas —multa, socio y sucursal— y sacarlas en cascada. Un error frecuente es descomponer solo un nivel y quedarse con SOCIOS(socio_id, socio_nombre, sucursal_id, sucursal_nombre, sucursal_telefono), que sigue sin estar en 3FN.


Ejercicio 6: El caso que está en 3FN pero no en FNBC

Dificultad: Intermedio

Enunciado. Un gabinete de asesoría de Vallmar asigna asesores a clientes por especialidad:

cliente especialidad asesor
Panadería Solé fiscal Rita Bonet
Panadería Solé laboral Marc Vidal
Taller Ferrer fiscal Rita Bonet
Taller Ferrer contable Nuria Gasch
Óptica Vilamar laboral Marc Vidal
Óptica Vilamar fiscal Sergi Prat

Reglas de negocio:

  • Cada asesor trabaja una única especialidad.
  • Para cada cliente y cada especialidad hay exactamente un asesor asignado.
  • Un cliente puede tener varios asesores (uno por especialidad) y un asesor puede llevar varios clientes.

Se pide: dependencias, todas las claves candidatas, atributos primos, forma normal exacta y dependencia culpable, y las anomalías.

Pista. Comprueba primero si algún atributo es no primo. La respuesta cambia todo el análisis.

Solución

m1: cliente, especialidad → asesor      (segunda regla)
m2: asesor               → especialidad (primera regla)

Claves candidatas. Calculamos cierres:

{cliente, especialidad}⁺ = {cliente, especialidad, asesor}  = TODO → superclave
{cliente, asesor}⁺       : asesor →(m2) especialidad        = TODO → superclave
{cliente}⁺               = {cliente}                        → no
{especialidad}⁺          = {especialidad}                   → no
{asesor}⁺                = {asesor, especialidad}           → no

Las dos superclaves de dos atributos son mínimas (ninguno de sus subconjuntos propios lo es). Claves candidatas: {cliente, especialidad} y {cliente, asesor}.

Atributos primos: cliente, especialidad, asesor. Los tres. No hay ningún atributo no primo.

Forma normal: 3FN, pero no FNBC.

  • 2FN: se cumple vacuamente. Las definiciones de 2FN y 3FN hablan de atributos no primos, y aquí no hay ninguno. Sin atributos no primos no puede haber dependencias parciales ni transitivas de atributos no primos.
  • 3FN: se cumple por lo mismo.
  • FNBC: no se cumple. La definición de FNBC no distingue primos de no primos: exige que el determinante de toda dependencia no trivial sea superclave. La dependencia culpable es m2: asesor → especialidad, porque {asesor}⁺ = {asesor, especialidad} no contiene a cliente: asesor no es superclave.

Anomalías.

Inserción. El gabinete ficha a Lidia Serna, especialista en mercantil. No hay forma de registrarlo: la clave primaria exige un cliente, y Lidia todavía no tiene ninguno. El dato «Lidia Serna es mercantilista» es inexpresable.

Borrado. Óptica Vilamar rescinde el contrato y se borran sus dos filas. Con la fila (Óptica Vilamar, fiscal, Sergi Prat) desaparece el único registro de que Sergi Prat es asesor fiscal: es su único cliente en la tabla.

Modificación. Rita Bonet se recicla y pasa de fiscal a contable. Su especialidad aparece en dos filas (Panadería Solé y Taller Ferrer). Si solo se actualiza una, la tabla afirma que Rita Bonet es fiscal para un cliente y contable para otro, violando la primera regla de negocio.

Resultado esperado

Concepto Valor
Claves candidatas {cliente, especialidad}, {cliente, asesor}
Atributos primos Los tres
Forma normal 3FN — no FNBC
Dependencia culpable asesor → especialidad (determinante no superclave)

Explicación. Este ejercicio existe porque es el contraejemplo que demuestra que la 3FN no basta. Todo el mundo llega a 3FN y da el trabajo por terminado; esta tabla está en 3FN de manual y sigue teniendo las tres anomalías clásicas.

La señal de alarma que hay que aprender a reconocer: una dependencia cuyo lado izquierdo es un solo atributo que también forma parte de una clave, pero que por sí solo no es clave. Aquí, asesor es primo (está en {cliente, asesor}) pero no es superclave. Esa configuración —determinante primo pero no superclave— es exactamente el hueco por el que la 3FN deja pasar redundancia.

La otra lección: el solapamiento de claves candidatas. Las dos claves comparten el atributo cliente. Siempre que dos claves candidatas se solapan, conviene comprobar la FNBC explícitamente, porque es la situación en la que la 3FN y la FNBC divergen. Si las claves candidatas son disjuntas o hay una sola, 3FN y FNBC coinciden en la práctica casi siempre.

La descomposición de esta tabla —y el problema serio que trae— es el ejercicio 8.


Bloque C — Avanzado: normalización completa

Ejercicio 7: De 1FN a 3FN, paso a paso y con el SQL de la migración

Dificultad: Avanzado

Enunciado. BiblioRed gestiona los pedidos a proveedores con esta tabla, tal cual la dejó la persona que se jubiló:

CREATE TABLE pedidos_plano (
    pedido_id           INTEGER,
    fecha               DATE,
    proveedor_id        VARCHAR(4),
    proveedor_nombre    VARCHAR(80),
    proveedor_cif       VARCHAR(12),
    proveedor_ciudad    VARCHAR(40),
    telefonos_proveedor VARCHAR(60),   -- ¡varios teléfonos separados por comas!
    lineas              TEXT           -- ¡varias líneas en una sola celda!
);

INSERT INTO pedidos_plano VALUES
 (5001,'2026-02-10','P1','Distribuidora Ponent','B12345678','Lleida','973551111, 610222333',
  '9788401337208 x3 @21.90; 9788401339097 x2 @19.50'),
 (5002,'2026-03-04','P2','Llibres Marina','B87654321','Vallmar','935559999',
  '9788483835609 x4 @12.95; 9788401337208 x1 @22.50'),
 (5003,'2026-05-20','P1','Distribuidora Ponent','B12345678','Lleida','973551111, 610222333',
  '9788483835609 x2 @13.20');

Datos de catálogo: 9788401337208 es «Los pilares de la Tierra» de Ken Follett; 9788401339097, «El mapa del tiempo» de Félix J. Palma; 9788483835609, «Tokio blues» de Haruki Murakami.

Lleva esta tabla de 1FN a 3FN, mostrando el estado de los datos después de cada forma normal y escribiendo el SQL de la descomposición.

Solución

Paso 0 → 1FN: atomizar.

La tabla ni siquiera está en 1FN: telefonos_proveedor es una lista con comas y lineas es un grupo repetitivo completo dentro de una celda. La 1FN exige valores atómicos y ningún grupo repetitivo.

Resultado tras la 1FN, con los teléfonos en su propia tabla y una fila por línea de pedido:

PEDIDOS_1FN(pedido_id, fecha, proveedor_id, proveedor_nombre, proveedor_cif, proveedor_ciudad, isbn, titulo, autor, unidades, precio_unit)

pedido_id fecha proveedor_id proveedor_nombre proveedor_cif proveedor_ciudad isbn titulo autor unidades precio_unit
5001 2026-02-10 P1 Distribuidora Ponent B12345678 Lleida 9788401337208 Los pilares de la Tierra Ken Follett 3 21.90
5001 2026-02-10 P1 Distribuidora Ponent B12345678 Lleida 9788401339097 El mapa del tiempo Félix J. Palma 2 19.50
5002 2026-03-04 P2 Llibres Marina B87654321 Vallmar 9788483835609 Tokio blues Haruki Murakami 4 12.95
5002 2026-03-04 P2 Llibres Marina B87654321 Vallmar 9788401337208 Los pilares de la Tierra Ken Follett 1 22.50
5003 2026-05-20 P1 Distribuidora Ponent B12345678 Lleida 9788483835609 Tokio blues Haruki Murakami 2 13.20

Y TELEFONOS_PROVEEDOR(proveedor_id, telefono) con cuatro filas: (P1, 973551111), (P1, 610222333), (P2, 935559999).

Análisis. Clave candidata de PEDIDOS_1FN: {pedido_id, isbn}. Dependencias:

n1: pedido_id, isbn → unidades, precio_unit
n2: pedido_id       → fecha, proveedor_id
n3: isbn            → titulo, autor
n4: proveedor_id    → proveedor_nombre, proveedor_cif, proveedor_ciudad
n5: proveedor_cif   → proveedor_id

Paso 1FN → 2FN: eliminar las dependencias parciales.

Culpables: n2 y n3. Ambas tienen como determinante un subconjunto propio de la clave y como dependiente atributos no primos.

-- Cabeceras de pedido (saca n2 y, de momento, arrastra los datos del proveedor)
CREATE TABLE pedidos_2fn AS
SELECT DISTINCT pedido_id, fecha, proveedor_id,
       proveedor_nombre, proveedor_cif, proveedor_ciudad
FROM pedidos_1fn;

-- Catálogo de libros (saca n3)
CREATE TABLE libros_2fn AS
SELECT DISTINCT isbn, titulo, autor
FROM pedidos_1fn;

-- Líneas de pedido: solo lo que depende de la clave entera
CREATE TABLE lineas_2fn AS
SELECT pedido_id, isbn, unidades, precio_unit
FROM pedidos_1fn;

Estado de los datos tras la 2FN:

pedidos_2fn (3 filas)

pedido_id fecha proveedor_id proveedor_nombre proveedor_cif proveedor_ciudad
5001 2026-02-10 P1 Distribuidora Ponent B12345678 Lleida
5002 2026-03-04 P2 Llibres Marina B87654321 Vallmar
5003 2026-05-20 P1 Distribuidora Ponent B12345678 Lleida

libros_2fn (3 filas)

isbn titulo autor
9788401337208 Los pilares de la Tierra Ken Follett
9788401339097 El mapa del tiempo Félix J. Palma
9788483835609 Tokio blues Haruki Murakami

lineas_2fn (5 filas)

pedido_id isbn unidades precio_unit
5001 9788401337208 3 21.90
5001 9788401339097 2 19.50
5002 9788483835609 4 12.95
5002 9788401337208 1 22.50
5003 9788483835609 2 13.20

Paso 2FN → 3FN: eliminar las dependencias transitivas.

pedidos_2fn sigue teniendo pedido_id → proveedor_id → proveedor_nombre, proveedor_cif, proveedor_ciudad. Es transitiva: proveedor_id es no primo en esa relación.

CREATE TABLE proveedores AS
SELECT DISTINCT proveedor_id, proveedor_nombre, proveedor_cif, proveedor_ciudad
FROM pedidos_2fn;

CREATE TABLE pedidos AS
SELECT pedido_id, fecha, proveedor_id
FROM pedidos_2fn;

Esquema final en 3FN, con claves y restricciones:

CREATE TABLE proveedores (
    proveedor_id     VARCHAR(4) PRIMARY KEY,
    proveedor_nombre VARCHAR(80) NOT NULL,
    proveedor_cif    VARCHAR(12) NOT NULL UNIQUE,   -- n5: clave alternativa
    proveedor_ciudad VARCHAR(40) NOT NULL
);

CREATE TABLE telefonos_proveedor (
    proveedor_id VARCHAR(4)  NOT NULL REFERENCES proveedores(proveedor_id) ON DELETE CASCADE,
    telefono     VARCHAR(15) NOT NULL,
    PRIMARY KEY (proveedor_id, telefono)
);

CREATE TABLE libros (
    isbn   CHAR(13) PRIMARY KEY,
    titulo VARCHAR(200) NOT NULL,
    autor  VARCHAR(120) NOT NULL
);

CREATE TABLE pedidos (
    pedido_id    INTEGER PRIMARY KEY,
    fecha        DATE NOT NULL,
    proveedor_id VARCHAR(4) NOT NULL REFERENCES proveedores(proveedor_id) ON DELETE RESTRICT
);

CREATE TABLE lineas_pedido (
    pedido_id   INTEGER  NOT NULL REFERENCES pedidos(pedido_id) ON DELETE CASCADE,
    isbn        CHAR(13) NOT NULL REFERENCES libros(isbn)       ON DELETE RESTRICT,
    unidades    SMALLINT NOT NULL CHECK (unidades > 0),
    precio_unit NUMERIC(8,2) NOT NULL CHECK (precio_unit >= 0),
    PRIMARY KEY (pedido_id, isbn)
);

Resultado esperado

Tabla Filas Contenido
proveedores 2 P1 Distribuidora Ponent (Lleida), P2 Llibres Marina (Vallmar)
telefonos_proveedor 3 P1 tiene dos, P2 tiene uno
libros 3 Los tres ISBN del catálogo
pedidos 3 5001, 5002, 5003
lineas_pedido 5 Las cinco líneas

Comprobación de que la descomposición es sin pérdida: reconstruir el original debe dar exactamente las cinco filas de PEDIDOS_1FN.

SELECT p.pedido_id, p.fecha, pr.proveedor_id, pr.proveedor_nombre,
       l.isbn, li.titulo, l.unidades, l.precio_unit
FROM lineas_pedido l
JOIN pedidos     p  ON p.pedido_id    = l.pedido_id
JOIN proveedores pr ON pr.proveedor_id = p.proveedor_id
JOIN libros      li ON li.isbn        = l.isbn
ORDER BY p.pedido_id, l.isbn;
-- 5 filas, idénticas a las originales

Explicación. Cuatro cosas que este ejercicio enseña y que no se ven en los ejemplos de juguete:

1. precio_unit se queda en lineas_pedido, y es lo correcto. La tentación es sacarlo al catálogo de libros, «porque el precio es del libro». No lo es: mira los datos. «Los pilares de la Tierra» se compró a 21,90 € en el pedido 5001 y a 22,50 € en el 5002, a proveedores distintos. Y «Tokio blues» a 12,95 € y a 13,20 €. El precio depende de la línea entera —{pedido_id, isbn}—, no del ISBN. Es una dependencia total, y por tanto se queda donde está. Sacarla habría sido una pérdida de información, no una normalización.

2. SELECT DISTINCT es el corazón de la migración. Cada CREATE TABLE ... AS SELECT DISTINCT proyecta las columnas de la nueva relación y deduplica. Es exactamente la proyección del álgebra relacional. Si tras el DISTINCT la nueva tabla tiene más filas de las esperadas, es la señal de que los datos contradicen la dependencia funcional: por ejemplo, si proveedores saliera con tres filas, significaría que en algún pedido «P1» aparece con otra ciudad, y habría que limpiar los datos antes de poner la clave primaria.

3. Los teléfonos salen en la 1FN, no en la 3FN. Un atributo multivaluado no es un problema de dependencias funcionales: es un problema de atomicidad. Se resuelve en el primer paso, creando una tabla cuya clave primaria es (proveedor_id, telefono).

4. proveedor_cif → proveedor_id (n5) no genera tabla nueva. Es una dependencia entre dos atributos que ya conviven en proveedores, y significa que el CIF es una clave alternativa. Se materializa con UNIQUE, no con una descomposición. Confundir «hay una dependencia» con «hay que descomponer» es un error frecuente: solo hay que descomponer cuando la dependencia viola la forma normal que persigues.

Sobre SQLite: CREATE TABLE ... AS SELECT funciona igual, pero no admite declarar claves ni restricciones en la misma instrucción, y ALTER TABLE ADD CONSTRAINT no existe. Allí el patrón es crear las tablas finales con su DDL completo y luego INSERT INTO ... SELECT DISTINCT ....


Ejercicio 8: FNBC sin conservación de dependencias

Dificultad: Avanzado

Enunciado. Retoma la tabla del ejercicio 6, ASESORIA(cliente, especialidad, asesor) con m1: {cliente, especialidad} → asesor y m2: asesor → especialidad.

  • (a) Descompón a FNBC eliminando la dependencia culpable.
  • (b) Demuestra que la descomposición es sin pérdida.
  • (c) Demuestra que no conserva las dependencias e identifica cuál se pierde.
  • (d) Muestra con datos concretos qué se puede colar en el esquema descompuesto que el original impedía.
  • (e) Decide qué hacer y justifica la decisión.

Solución

(a) Descomposición. El procedimiento estándar para llegar a FNBC: dada la dependencia culpable X → Y (aquí asesor → especialidad), se descompone en X ∪ Y y en R − Y.

R1(asesor, especialidad)     ← la dependencia culpable, ahora con asesor como clave
R2(cliente, asesor)          ← el resto

Ambas están en FNBC: en R1 la única dependencia es asesor → especialidad y asesor es la clave; en R2 la única dependencia es la trivial y la clave es {cliente, asesor}, la relación entera.

Con los datos del ejercicio 6:

R1 (4 filas)

asesor especialidad
Rita Bonet fiscal
Marc Vidal laboral
Nuria Gasch contable
Sergi Prat fiscal

R2 (6 filas)

cliente asesor
Panadería Solé Rita Bonet
Panadería Solé Marc Vidal
Taller Ferrer Rita Bonet
Taller Ferrer Nuria Gasch
Óptica Vilamar Marc Vidal
Óptica Vilamar Sergi Prat

(b) Sin pérdida. El criterio: una descomposición binaria de R en R1 y R2 es sin pérdida si los atributos comunes determinan funcionalmente a todos los de al menos una de las dos.

R1 ∩ R2 = {asesor}
{asesor} → especialidad          (por m2)
{asesor} → {asesor, especialidad} = R1     ✓

{asesor} es clave de R1, luego la descomposición es sin pérdida. Comprobación con datos: el JOIN de R1 y R2 por asesor devuelve exactamente las 6 filas originales, ni una más.

SELECT r2.cliente, r1.especialidad, r2.asesor
FROM r2 JOIN r1 ON r1.asesor = r2.asesor;
-- 6 filas, idénticas al original

(c) No conserva las dependencias. La proyección de F sobre las dos relaciones da:

F₁ (sobre R1) = { asesor → especialidad }
F₂ (sobre R2) = { }   (ninguna dependencia no trivial)
F₁ ∪ F₂ = { asesor → especialidad }

La dependencia m1: {cliente, especialidad} → asesor se ha perdido. No se deduce de F₁ ∪ F₂: el cierre de {cliente, especialidad} bajo F₁ ∪ F₂ es {cliente, especialidad} y no contiene asesor.

Y lo que es peor en la práctica: no se puede comprobar mirando una sola tabla. R1 no conoce a los clientes; R2 no conoce las especialidades. Verificarla exige unir las dos.

(d) Qué se cuela ahora. Insertamos en R2 una fila perfectamente legal para el esquema descompuesto:

INSERT INTO r2 (cliente, asesor) VALUES ('Panadería Solé', 'Sergi Prat');

Ninguna restricción se queja: R2 solo exige que la pareja no se repita, y no se repite. Pero al reconstruir:

cliente especialidad asesor
Panadería Solé fiscal Rita Bonet
Panadería Solé fiscal Sergi Prat
...

Panadería Solé tiene dos asesores fiscales. La regla de negocio «para cada cliente y especialidad hay exactamente un asesor» está rota, y el esquema original en 3FN la impedía con su clave primaria. La FNBC ha eliminado la redundancia a costa de perder una garantía.

(e) La decisión. Hay tres salidas defendibles y una que no lo es:

Opción Qué se hace Se gana Se pierde
1. Quedarse en 3FN Una sola tabla ASESORIA(cliente, especialidad, asesor) con PK (cliente, especialidad) La regla m1 la garantiza la clave primaria La redundancia y las tres anomalías del ejercicio 6
2. FNBC + comprobación programada R1 y R2, y un disparador sobre R2 que consulte R1 Cero redundancia Complejidad; la regla depende de código, no del esquema
3. FNBC «reconstruida» (recomendada) R1(asesor, especialidad) con PK asesor; R2(cliente, asesor, especialidad) con FK compuesta a R1 y UNIQUE(cliente, especialidad) Las dos reglas garantizadas por el motor Una columna redundante (especialidad en R2), pero blindada por la FK compuesta
4. FNBC «a pelo» R1 y R2 sin más Nada La regla m1, sin sustituto. No hacer esto

La opción 3 merece verse escrita, porque es el patrón que resuelve la mayoría de estos casos en la práctica:

CREATE TABLE asesores (
    asesor       VARCHAR(80) PRIMARY KEY,
    especialidad VARCHAR(20) NOT NULL,
    CONSTRAINT uq_asesor_esp UNIQUE (asesor, especialidad)  -- para la FK compuesta
);

CREATE TABLE asignaciones (
    cliente      VARCHAR(80) NOT NULL,
    asesor       VARCHAR(80) NOT NULL,
    especialidad VARCHAR(20) NOT NULL,
    PRIMARY KEY (cliente, asesor),
    -- La especialidad de la asignación TIENE que ser la del asesor
    CONSTRAINT fk_asig_asesor FOREIGN KEY (asesor, especialidad)
        REFERENCES asesores (asesor, especialidad) ON UPDATE CASCADE,
    -- Un solo asesor por cliente y especialidad
    CONSTRAINT uq_asig_cliente_esp UNIQUE (cliente, especialidad)
);

-- Ahora el intento del apartado (d) falla:
INSERT INTO asignaciones VALUES ('Panadería Solé','Sergi Prat','fiscal');
-- ERROR: duplicate key value violates unique constraint "uq_asig_cliente_esp"

Resultado esperado

Pregunta Respuesta
Descomposición FNBC R1(asesor, especialidad) + R2(cliente, asesor)
¿Sin pérdida? : R1 ∩ R2 = {asesor} y asesor es clave de R1
¿Conserva dependencias? No: se pierde {cliente, especialidad} → asesor
Consecuencia práctica Un cliente puede acabar con dos asesores de la misma especialidad
Decisión recomendada Opción 3: FNBC con especialidad replicada y protegida por FK compuesta + UNIQUE

Explicación. Este es el ejercicio que separa a quien ha memorizado las formas normales de quien las entiende. El teorema de fondo es contundente:

Toda relación admite una descomposición a 3FN sin pérdida y con conservación de dependencias. No toda relación admite una descomposición a FNBC con conservación de dependencias.

La FNBC es más estricta y a veces es demasiado estricta: elimina la redundancia al precio de que una regla de negocio deje de ser verificable dentro de una sola tabla. Cuando eso pasa, la normalización deja de ser una decisión técnica y pasa a ser una decisión de negocio: ¿qué duele más, la redundancia o el riesgo de datos incoherentes?

En la práctica, la respuesta casi siempre es que duele más el riesgo de incoherencia, porque la redundancia se puede vigilar con una consulta de auditoría y la incoherencia, una vez ocurrida, es muy cara de limpiar. Por eso la opción 3 —replicar el atributo y blindarlo con una clave ajena compuesta, el mismo truco que cerraba la jerarquía del taller mecánico en 07-02— es el compromiso que más veces gana: se paga una columna repetida, pero el motor garantiza que nunca se descuadre.

La opción 1 (quedarse en 3FN) es perfectamente respetable cuando el volumen es pequeño y la tabla se consulta poco. Lo que nunca es respetable es la opción 4: descomponer, perder la dependencia y no decírselo a nadie.


Ejercicio 9: Cuarta forma normal y dependencias multivaluadas

Dificultad: Avanzado

Enunciado. Para preparar el material de los eventos, BiblioRed llevaba esta tabla:

EVENTO_RECURSOS(evento_id, ponente, material)

donde se anotan los ponentes que participan en un evento y los materiales que se exponen en él. Los ponentes y los materiales son independientes entre sí: qué materiales se exponen no depende de qué ponente hable, y viceversa.

Datos del evento 104 («Presentación: La caja de los deseos»), con dos ponentes y dos materiales:

evento_id ponente material
104 Delia Marchetti La caja de los deseos
104 Delia Marchetti Ciencia Vallmar 42
104 Rosa Calduch La caja de los deseos
104 Rosa Calduch Ciencia Vallmar 42

Se pide:

  • (a) Determinar la clave candidata y comprobar si está en FNBC.
  • (b) Escribir las dependencias multivaluadas.
  • (c) Descomponer a 4FN y mostrar las tablas con datos.
  • (d) Cuantificar el ahorro si el evento tuviera 3 ponentes y 4 materiales.

Solución

(a) La clave candidata es {evento_id, ponente, material}: la relación entera. No hay ninguna dependencia funcional no trivial —ni evento_id → ponente (hay dos), ni ponente → material (cada ponente aparece con los dos materiales)—, así que la única forma de identificar una fila es con los tres atributos.

Como todos los atributos son primos y no hay dependencias funcionales no triviales, la tabla está en FNBC. Y sin embargo la redundancia salta a la vista.

(b) Dependencias multivaluadas. Una dependencia multivaluada X ↠ Y dice que el conjunto de valores de Y asociado a un valor de X es independiente del resto de atributos. Aquí:

evento_id ↠ ponente
evento_id ↠ material

Las dos son no triviales (ponente no contiene a evento_id, y evento_id ∪ ponente ≠ R) y evento_id no es superclave. Por tanto la tabla no está en 4FN.

La señal empírica de una dependencia multivaluada: la tabla contiene el producto cartesiano de dos conjuntos independientes. 2 ponentes × 2 materiales = 4 filas, y las 4 tienen que estar. Si faltara la fila (104, Rosa Calduch, Ciencia Vallmar 42), la tabla estaría afirmando algo falso: que Rosa Calduch tiene algo que ver con qué material se expone.

(c) Descomposición a 4FN.

CREATE TABLE evento_ponentes AS
SELECT DISTINCT evento_id, ponente FROM evento_recursos;

CREATE TABLE evento_materiales AS
SELECT DISTINCT evento_id, material FROM evento_recursos;

evento_ponentes (2 filas)

evento_id ponente
104 Delia Marchetti
104 Rosa Calduch

evento_materiales (2 filas)

evento_id material
104 La caja de los deseos
104 Ciencia Vallmar 42

La descomposición es sin pérdida por el teorema de Fagin: si X ↠ Y se cumple en R, entonces R se descompone sin pérdida en R[X ∪ Y] y R[X ∪ (R − Y)]. El JOIN por evento_id reconstruye exactamente las 4 filas originales.

(d) El ahorro. Con 3 ponentes y 4 materiales:

Esquema Filas
EVENTO_RECURSOS (una tabla) 3 × 4 = 12
evento_ponentes + evento_materiales 3 + 4 = 7

Y con 5 ponentes y 20 materiales: 100 filas frente a 25. El crecimiento es multiplicativo en el esquema sin normalizar y aditivo en el normalizado.

Resultado esperado

Concepto Valor
Clave candidata {evento_id, ponente, material} (la relación entera)
¿FNBC?
¿4FN? No: evento_id ↠ ponente y evento_id ↠ material con evento_id no superclave
Descomposición evento_ponentes(evento_id, ponente) + evento_materiales(evento_id, material)
Ahorro con 3×4 De 12 filas a 7

Explicación. La 4FN es la primera forma normal que no se puede diagnosticar mirando dependencias funcionales, y por eso se escapa tan a menudo. La tabla está en FNBC —el análisis funcional dice que está perfecta— y aun así tiene una anomalía brutal de actualización: añadir un tercer ponente al evento 104 obliga a insertar dos filas, una por cada material, y olvidar una deja la tabla en un estado que afirma una relación que no existe.

El criterio práctico para detectar una dependencia multivaluada, más útil que la definición formal:

Si al añadir un valor nuevo de Y tienes que insertar una fila por cada valor existente de Z, y Y y Z no tienen nada que ver entre sí, hay una dependencia multivaluada.

Cuándo NO hay dependencia multivaluada, que es el error simétrico. Si en BiblioRed cada ponente presentara su propio material —Delia Marchetti su novela y Rosa Calduch la revista—, entonces ponente y material estarían relacionados, no habría producto cartesiano, la tabla estaría en 4FN y descomponerla perdería información: al reconstruir con un JOIN aparecerían las parejas falsas. La independencia entre los dos conjuntos es una regla de negocio, no algo que se deduzca de los datos, y es lo primero que hay que confirmar antes de descomponer.

Nota: en el esquema real de BiblioRed esta descomposición ya está hecha —participaciones y eventos_materiales son exactamente las dos tablas del apartado (c)—. El ejercicio muestra de dónde viene esa decisión de diseño.


Ejercicio 10: Desnormalización justificada

Dificultad: Avanzado

Enunciado. El catálogo público de BiblioRed muestra, para cada material, cuántos ejemplares disponibles hay en cada sucursal. Es la consulta más ejecutada del sistema: unas 400 veces por minuto en hora punta. Con 40.000 ejemplares, la consulta actual

SELECT e.sucursal_id, count(*) AS disponibles
FROM ejemplares e
WHERE e.material_id = 902 AND e.estado = 'disponible'
GROUP BY e.sucursal_id;

tarda 380 ms incluso con índice, porque el catálogo la ejecuta una vez por cada material de la página de resultados (hasta 20 materiales) y eso son casi 8 segundos por página.

En cambio, los cambios de estado de un ejemplar son unos 3.000 al día: préstamos, devoluciones, retiradas.

Se pide:

  • (a) Proponer qué desnormalizar y por qué esa opción y no otra.
  • (b) Escribir el SQL de la estructura desnormalizada.
  • (c) Explicar cómo se mantiene la coherencia.
  • (d) Enumerar qué se acepta a cambio.

Solución

(a) Qué desnormalizar: una tabla resumen.

Comparemos las tres opciones del catálogo de 05-04:

Opción Descripción Valoración
Columna calculada en materiales materiales.n_disponibles Insuficiente: la pregunta es por sucursal, y una columna escalar no puede almacenar cuatro valores
Vista materializada CREATE MATERIALIZED VIEW ... REFRESH Descartada: REFRESH MATERIALIZED VIEW recalcula los 40.000 ejemplares enteros. Con 3.000 cambios al día habría que refrescar cada pocos minutos, y entre refrescos el catálogo miente sobre disponibilidad
Tabla resumen con disparadores disponibilidad(material_id, sucursal_id, n_disponibles) Elegida: se actualiza incrementalmente (una fila por cada cambio), no por recálculo completo, y la lectura es un acceso directo por clave primaria

(b) La estructura.

CREATE TABLE disponibilidad (
    material_id   INTEGER  NOT NULL REFERENCES materiales(material_id) ON DELETE CASCADE,
    sucursal_id   INTEGER  NOT NULL REFERENCES sucursales(sucursal_id) ON DELETE CASCADE,
    n_disponibles INTEGER  NOT NULL DEFAULT 0 CHECK (n_disponibles >= 0),
    actualizado   TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (material_id, sucursal_id)
);

-- Carga inicial desde la fuente de verdad
INSERT INTO disponibilidad (material_id, sucursal_id, n_disponibles)
SELECT material_id, sucursal_id, count(*)
FROM ejemplares
WHERE estado = 'disponible'
GROUP BY material_id, sucursal_id;

La consulta del catálogo pasa a ser:

SELECT sucursal_id, n_disponibles
FROM disponibilidad
WHERE material_id = 902 AND n_disponibles > 0;

Un acceso por clave primaria: menos de 1 ms, frente a los 380 ms del GROUP BY.

(c) Cómo se mantiene la coherencia: un disparador sobre ejemplares.

CREATE OR REPLACE FUNCTION fn_sync_disponibilidad() RETURNS trigger AS $$
BEGIN
    -- Restar del estado anterior, si era disponible
    IF (TG_OP = 'UPDATE' OR TG_OP = 'DELETE') AND OLD.estado = 'disponible' THEN
        UPDATE disponibilidad
           SET n_disponibles = n_disponibles - 1, actualizado = now()
         WHERE material_id = OLD.material_id AND sucursal_id = OLD.sucursal_id;
    END IF;

    -- Sumar al estado nuevo, si es disponible
    IF (TG_OP = 'UPDATE' OR TG_OP = 'INSERT') AND NEW.estado = 'disponible' THEN
        INSERT INTO disponibilidad (material_id, sucursal_id, n_disponibles)
        VALUES (NEW.material_id, NEW.sucursal_id, 1)
        ON CONFLICT (material_id, sucursal_id)
        DO UPDATE SET n_disponibles = disponibilidad.n_disponibles + 1,
                      actualizado   = now();
    END IF;

    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER tg_sync_disponibilidad
AFTER INSERT OR UPDATE OF estado, sucursal_id, material_id OR DELETE ON ejemplares
FOR EACH ROW EXECUTE FUNCTION fn_sync_disponibilidad();

Y una consulta de reconciliación nocturna que compara el resumen con la fuente de verdad y reporta cualquier descuadre:

WITH real AS (
    SELECT material_id, sucursal_id, count(*) AS n
    FROM ejemplares WHERE estado = 'disponible'
    GROUP BY material_id, sucursal_id
)
SELECT COALESCE(r.material_id, d.material_id) AS material_id,
       COALESCE(r.sucursal_id, d.sucursal_id) AS sucursal_id,
       COALESCE(r.n, 0)             AS deberia_ser,
       COALESCE(d.n_disponibles, 0) AS esta_guardado
FROM real r
FULL OUTER JOIN disponibilidad d
  ON d.material_id = r.material_id AND d.sucursal_id = r.sucursal_id
WHERE COALESCE(r.n, 0) <> COALESCE(d.n_disponibles, 0);
-- Debe devolver 0 filas. Si devuelve alguna, hay un descuadre que investigar.

(d) Qué se acepta a cambio.

Coste aceptado Magnitud
Escrituras más lentas Cada cambio de estado dispara 1 o 2 UPDATE extra. 3.000 al día es despreciable frente a 400 lecturas/minuto
Contención en filas calientes Todos los préstamos de «El mapa del tiempo» en Centro compiten por la misma fila de disponibilidad. Es un punto de serialización; en un sistema con mucho más volumen habría que fragmentar el contador
Riesgo de descuadre Un UPDATE masivo que esquive el disparador, un error en la función o una restauración parcial dejan el resumen desfasado. Por eso la reconciliación nocturna no es opcional
Complejidad de mantenimiento Hay código de negocio en la base de datos. Quien modifique el esquema de ejemplares tiene que saber que existe el disparador
Un dato con dos fuentes ejemplares es la fuente de verdad; disponibilidad es una copia. Toda duda se resuelve siempre a favor de ejemplares

Resultado esperado

Métrica Antes Después
Consulta de un material 380 ms < 1 ms
Página de catálogo (20 materiales) ~7,6 s ~20 ms
Coste por cambio de estado 1 UPDATE 1 UPDATE + 1-2 en disponibilidad
Formas normales del esquema 3FN 3FN más una tabla derivada documentada

Explicación. La desnormalización es una decisión de ingeniería, no un fracaso del diseño, y por eso tiene que estar justificada con números. Los tres números que justifican esta son: 400 lecturas por minuto, 3.000 escrituras al día y 380 ms por consulta. Sin esos tres, la propuesta sería una opinión.

La regla que gobierna la decisión: desnormaliza cuando la proporción lectura/escritura es muy alta y el coste de la consulta es estructural (un GROUP BY sobre muchas filas no se arregla con un índice mejor). Aquí la proporción es de unas 200 lecturas por cada escritura.

Tres detalles del diseño que conviene no perder:

  • ON CONFLICT ... DO UPDATE (el «upsert» de PostgreSQL) evita tener que comprobar si la fila existe. Es imprescindible: el primer ejemplar disponible de un material en una sucursal no tiene fila previa.
  • El disparador se declara AFTER ... OF estado, sucursal_id, material_id. Restringir las columnas evita que un UPDATE de portada_url dispare trabajo inútil.
  • CHECK (n_disponibles >= 0) es un canario. Si el contador intenta bajar de cero, hay un error en la lógica y es mejor que la transacción falle ruidosamente que dejar el resumen mintiendo en silencio.

Y la advertencia final: la tabla disponibilidad no debe usarse nunca para tomar decisiones transaccionales. Para saber si se puede prestar un ejemplar concreto hay que ir a ejemplares con un bloqueo, no al resumen. El resumen es para pintar el catálogo. Confundir un dato derivado con la fuente de verdad es la forma más habitual de que una desnormalización acabe prestando el mismo ejemplar dos veces.


Errores Comunes y Consejos

1. Deducir las dependencias funcionales de los datos. Los datos solo pueden refutar una dependencia, nunca demostrarla. Que en seis filas cada asesor tenga una sola especialidad no prueba asesor → especialidad; lo prueba la regla de negocio. Pregunta siempre «¿puede cambiar esto?» antes de escribir una flecha.

2. Parar en la primera clave candidata. Es el error del ejercicio 2. Antes de evaluar formas normales hay que tener todas las claves candidatas, porque una sola de ellas basta para que la relación incumpla una forma normal.

3. Confundir superclave con clave candidata. Una superclave determina todos los atributos; una clave candidata es una superclave mínima. {evento, sala} es superclave y no es clave candidata.

4. Calcular el cierre con una sola pasada. Hay que iterar hasta que el conjunto deje de crecer. Muchas dependencias solo se disparan después de que otra haya añadido su atributo.

5. Saltarse la 2FN cuando la clave es simple. No es un error, es un atajo legítimo: con clave candidata única y de un atributo, la 2FN se cumple siempre. Lo que sí es un error es dar por hecho lo mismo para la 3FN.

6. Descomponer toda dependencia que aparezca. proveedor_cif → proveedor_id no exige una tabla nueva: es una clave alternativa y se resuelve con UNIQUE. Solo se descompone lo que viola la forma normal que persigues.

7. Sacar de la tabla de detalle un atributo que depende de la clave entera. El precio_unit del ejercicio 7 depende de {pedido_id, isbn}, no del ISBN. Sacarlo al catálogo destruye información. Comprueba siempre si el valor cambia entre filas con el mismo determinante candidato.

8. Descomponer a FNBC sin comprobar la conservación de dependencias. Es el ejercicio 8. Antes de aplicar el algoritmo, proyecta F sobre las relaciones resultantes y comprueba qué has perdido.

9. Confundir dependencia funcional con multivaluada. Si los dos conjuntos son independientes (producto cartesiano obligado), es multivaluada y hay que descomponer. Si están relacionados, descomponer inventa filas al reconstruir.

10. Desnormalizar sin números y sin reconciliación. Una desnormalización sin medición previa es una superstición, y una sin proceso de reconciliación es una bomba de relojería.

Consejo de método. Trabaja siempre en este orden y no lo cambies: dependencias → cierres → todas las claves candidatas → primos y no primos → formas normales en orden. Saltarse un paso hace que el diagnóstico salga mal en la mitad de los casos, y lo peor es que sale mal de forma plausible.

Consejo de comprobación. Después de cada descomposición, cuenta las filas de las tablas nuevas y reconstruye el original con un JOIN. Si al reconstruir salen más filas que las originales, la descomposición tiene pérdida (has generado tuplas espurias). Si salen menos, has perdido datos. Solo si salen exactamente las mismas está bien.

Ejercicios

Sin pistas. Escribe el análisis completo en cada uno.

Ejercicio A: Normalización completa de la agenda de salas

Retoma OCUPACION(sala, dia, hora, evento, aforo, sucursal) del ejercicio 2, con:

g1: sala            → aforo, sucursal
g2: sala, dia, hora → evento
g3: evento          → sala, dia, hora

Llévala a FNBC, mostrando la descomposición paso a paso, comprobando que es sin pérdida y diciendo si conserva las dependencias. Escribe el esquema final con sus claves y sus restricciones.

Ejercicio B: Diagnóstico de la hoja de ruta de reparto

Una empresa de reparto de Vallmar usa esta tabla:

ruta_id fecha conductor_id conductor_nombre vehiculo_matricula vehiculo_carga_kg parada_orden cliente direccion bultos
R-100 2026-07-06 C4 Nuria Gasch 4471 KLM 900 1 Panadería Solé C/ Marina 12 6
R-100 2026-07-06 C4 Nuria Gasch 4471 KLM 900 2 Taller Ferrer Avda. Bosque 40 2
R-101 2026-07-06 C7 Aitor Lemus 8820 BNM 1400 1 Óptica Vilamar Plaza Mayor 3 1
R-102 2026-07-07 C4 Nuria Gasch 8820 BNM 1400 1 Panadería Solé C/ Marina 12 4

Reglas: cada ruta la hace un conductor con un vehículo en una fecha; dentro de una ruta las paradas van numeradas; cada cliente tiene una única dirección; cada vehículo tiene una carga máxima. Determina claves candidatas, forma normal exacta, dependencia culpable y una anomalía de cada tipo nombrando fila y dato.

Ejercicio C: Decisión de desnormalización en el panel de dirección

La dirección de BiblioRed quiere un panel que se abre 30 veces al día y muestra, para cada uno de los últimos 24 meses: préstamos totales, socios distintos que pidieron algo, multas emitidas e importe recaudado. La consulta actual recorre prestamos, multas y pagos enteros —unos 900.000 registros— y tarda 4 segundos. Los datos de meses cerrados no cambian nunca. Propón la desnormalización, justifícala, di cómo se mantiene y qué se acepta a cambio. Compara explícitamente con la solución del ejercicio 10 y explica por qué aquí la elección es distinta.

Soluciones

Solución A

Estado inicial. Claves candidatas {evento} y {sala, dia, hora}; primos: evento, sala, dia, hora; no primos: aforo, sucursal. Está solo en 1FN, porque g1 es parcial respecto a {sala, dia, hora}.

Paso a 2FN/3FN. El determinante culpable es sala. Se saca:

SALAS(sala, aforo, sucursal)                 -- clave: {sala}
AGENDA(sala, dia, hora, evento)              -- claves: {sala,dia,hora} y {evento}

SALAS está en FNBC: su única dependencia es sala → aforo, sucursal y sala es la clave.

AGENDA conserva g2 y g3. Sus dos claves candidatas siguen siendo {sala, dia, hora} y {evento}; todos sus atributos son primos, así que está en 3FN. ¿Y en FNBC? Las dos dependencias que quedan son g2 y g3, y en ambas el determinante es una clave candidata, luego superclave. AGENDA está en FNBC.

Sin pérdida. SALAS ∩ AGENDA = {sala}, y sala → aforo, sucursal hace de {sala} la clave de SALAS. Cumple el criterio.

Conservación de dependencias. g1 vive entera en SALAS; g2 y g3 viven enteras en AGENDA. F₁ ∪ F₂ = F: se conservan todas. Este caso es el habitual y contrasta con el ejercicio 8: aquí la FNBC sale gratis.

Esquema final:

CREATE TABLE salas (
    sala      VARCHAR(40) PRIMARY KEY,
    aforo     SMALLINT NOT NULL CHECK (aforo > 0),
    sucursal  VARCHAR(40) NOT NULL
);

CREATE TABLE agenda (
    evento VARCHAR(120) PRIMARY KEY,           -- clave candidata elegida como primaria
    sala   VARCHAR(40) NOT NULL REFERENCES salas(sala) ON UPDATE CASCADE,
    dia    DATE NOT NULL,
    hora   TIME NOT NULL,
    CONSTRAINT uq_agenda_slot UNIQUE (sala, dia, hora)   -- la otra clave candidata
);

Lo importante del esquema final: las dos claves candidatas quedan declaradas, una como PRIMARY KEY y la otra como UNIQUE. Declarar solo una de las dos dejaría g2 sin garantía y permitiría dos eventos en la misma sala a la misma hora.

Solución B

Claves candidatas. Una fila es una parada dentro de una ruta: {ruta_id, parada_orden}. Comprobación: {ruta_id, parada_orden}⁺ alcanza cliente, y de ahí direccion; ruta_id da fecha, conductor_id, vehiculo_matricula; y de ahí conductor_nombre y vehiculo_carga_kg. Cierre completo. Nada más corto lo consigue.

Dependencias:

d1: ruta_id, parada_orden → cliente, bultos
d2: ruta_id               → fecha, conductor_id, vehiculo_matricula
d3: conductor_id          → conductor_nombre
d4: vehiculo_matricula    → vehiculo_carga_kg
d5: cliente               → direccion

Forma normal: 1FN, no 2FN. La dependencia culpable es d2: ruta_id es subconjunto propio de la clave y fecha, conductor_id y vehiculo_matricula son no primos. d3, d4 y d5 son además transitivas y se resolverían en el paso a 3FN.

Anomalías con fila y dato exactos:

  • Modificación. El vehículo 8820 BNM recibe una revisión y su carga máxima pasa a 1.300 kg. El valor 1400 aparece en dos filas: la de R-101 y la de R-102. Si solo se actualiza la de R-101, la tabla afirma que el mismo vehículo tiene dos cargas máximas distintas según el día.
  • Inserción. Se contrata a un conductor nuevo, C9 / Berta Quintana. No se puede registrar hasta que se le asigne una ruta con al menos una parada. Lo mismo con un cliente nuevo: su dirección no existe hasta que tenga un reparto.
  • Borrado. Se cancela la parada de Óptica Vilamar en la ruta R-101 y se borra esa fila. Como es la única fila de R-101, desaparece la ruta entera: que existió el 6 de julio, que la hizo el conductor C7 (Aitor Lemus) y que usó el vehículo 8820 BNM. Y con ella se pierde también que Óptica Vilamar está en Plaza Mayor 3, porque es su única aparición... salvo que quedara otra, cosa que aquí no ocurre.

Descomposición a 3FN: clientes(cliente, direccion), conductores(conductor_id, conductor_nombre), vehiculos(vehiculo_matricula, vehiculo_carga_kg), rutas(ruta_id, fecha, conductor_id, vehiculo_matricula), paradas(ruta_id, parada_orden, cliente, bultos).

Solución C

Propuesta: una tabla resumen de meses cerrados, poblada por un proceso mensual.

CREATE TABLE resumen_mensual (
    anio             SMALLINT NOT NULL,
    mes              SMALLINT NOT NULL CHECK (mes BETWEEN 1 AND 12),
    prestamos        INTEGER  NOT NULL,
    socios_distintos INTEGER  NOT NULL,
    multas_emitidas  INTEGER  NOT NULL,
    importe_multas   NUMERIC(12,2) NOT NULL,
    recaudado        NUMERIC(12,2) NOT NULL,
    cerrado          BOOLEAN NOT NULL DEFAULT TRUE,
    calculado_en     TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (anio, mes)
);

Cómo se mantiene. Un proceso programado el día 1 de cada mes inserta la fila del mes recién cerrado con un único recorrido de las tablas de origen. El mes en curso no se guarda: se calcula al vuelo, porque es el único que cambia. El panel une las 23 filas del resumen con el cálculo en vivo del mes actual mediante un UNION ALL.

Por qué la elección es distinta a la del ejercicio 10. La diferencia decisiva es la volatilidad del dato:

Ejercicio 10 (disponibilidad) Ejercicio C (resumen mensual)
¿Cambia el dato ya calculado? , 3.000 veces al día No: un mes cerrado es inmutable
Frecuencia de lectura 400/min 30/día
Mecanismo adecuado Disparador incremental Proceso programado (o vista materializada)
Riesgo de descuadre Alto: cada cambio puede fallar Muy bajo: se calcula una vez, sobre datos que ya no se mueven
Tolerancia al desfase Cero: el catálogo mentiría Total: nadie espera el dato de julio el 1 de julio a las 00:00

Un disparador sobre prestamos para mantener el resumen mensual sería desproporcionado: añadiría trabajo a cada préstamo para servir 30 lecturas al día. Y una vista materializada, que en el ejercicio 10 era mala idea porque recalcula todo, aquí es perfectamente razonable: se refresca una vez al mes y el recálculo completo no molesta a nadie.

Qué se acepta a cambio: el mes en curso se sigue calculando al vuelo (unos 200 ms, aceptable); hay que vigilar que ninguna corrección retroactiva toque un mes ya cerrado, y si la hay, recalcular esa fila explícitamente; y el panel necesita el UNION ALL, que es una complicación de la consulta que no existía antes.

Conclusión

Has recorrido la normalización de punta a punta con diez ejercicios: has escrito conjuntos de dependencias funcionales a partir de reglas de negocio, calculado cierres paso a paso, encontrado todas las claves candidatas de relaciones con claves solapadas, clasificado atributos en primos y no primos, y visto que una misma dependencia se llama parcial o transitiva según la clave que tomes de referencia.

En el bloque de diagnóstico has puesto nombre y apellidos a las anomalías: no «hay redundancia», sino «el correo de Rosa Calduch está en cuatro filas y basta con que una se quede sin actualizar». Esa precisión es lo que convierte un análisis en un argumento con el que convencer a alguien de que hay que rehacer una tabla.

Y en el bloque avanzado has hecho el trabajo completo: la migración de 1FN a 3FN con su SQL de descomposición y su comprobación de reconstrucción; el caso incómodo en el que llegar a FNBC cuesta una dependencia y hay que decidir entre redundancia y garantía —con el patrón de la clave ajena compuesta como mejor compromiso—; la cuarta forma normal y las dependencias multivaluadas, que ninguna cantidad de análisis funcional detecta; y la desnormalización razonada con números, con su mecanismo de mantenimiento y su reconciliación obligatoria.

Si te quedas con una sola idea, que sea esta: normalizar no es aplicar reglas, es hacer que cada hecho viva en un solo sitio. Las formas normales son la manera formal de comprobar si lo has conseguido, y la desnormalización es la decisión consciente de romper esa regla en un punto concreto, con una medición delante y un proceso de vigilancia detrás.

La última lección del módulo, 07-04, Ejercicios de Consultas Avanzadas y Transacciones, es la más exigente de las cuatro y la que más se parece al trabajo real de producción. Volverás a BiblioRed para escribir funciones de ventana —el material más prestado de cada sucursal, la comparación mes contra mes con LAG, los acumulados—, una CTE recursiva, consultas sobre jsonb en las respuestas de las encuestas de eventos, y un pivote manual. Después llegan las transacciones: la del préstamo completa con su control de errores, SAVEPOINT en un proceso por lotes, y cinco ejercicios de dos sesiones psql en paralelo donde reproducirás una actualización perdida, verás la diferencia entre READ COMMITTED y REPEATABLE READ, provocarás un interbloqueo a propósito, implementarás el bloqueo optimista de la última plaza y consumirás una cola con SKIP LOCKED. Y para cerrar, cuatro ejercicios de índices y planes de ejecución. Ten los dos terminales listos.

Fundamentos de Bases de Datos

Módulo 1: Introducción a las Bases de Datos

Módulo 2: Bases de Datos Relacionales

Módulo 3: Bases de Datos No Relacionales

Módulo 4: Diseño de Esquemas

Módulo 5: Normalización

Módulo 6: Transacciones, Rendimiento y Seguridad

Módulo 7: Ejercicios Prácticos

Módulo 8: Casos de Estudio

Módulo 9: Recursos Adicionales

© Copyright 2026. Todos los derechos reservados