Has aprendido que la Primera Forma Normal elimina los valores múltiples en celdas y las columnas repetidas. La Segunda Forma Normal elimina los datos que dependen solo de una parte de la clave primaria compuesta. Con esas dos reglas el diseño ya es mucho más limpio. Pero todavía puede quedar un tipo de redundancia oculta que ninguna de las dos detecta.
Imagina una tabla donde cada columna depende correctamente de la clave primaria completa. A simple vista parece perfecta. Sin embargo, si una de esas columnas determina el valor de otra columna sin pasar por la clave, los datos se duplican, las actualizaciones se vuelven peligrosas y la integridad sigue en riesgo.
Ese problema lo resuelve la Tercera Forma Normal (3FN).
El concepto central: dependencia transitiva
Antes de ver ejemplos, necesitas entender qué es una dependencia transitiva. Es el tipo de dependencia que viola la 3FN.
Una dependencia transitiva ocurre cuando:
- La columna A determina la columna B:
A → B - La columna B determina la columna C:
B → C - Por lo tanto, A determina C de forma indirecta, a través de B:
A → B → C - Y B no es una clave candidata de la tabla.
Cuando A es la clave primaria, decimos que C depende transitivamente de la clave a través de B. Eso es una violación de la 3FN.
Un ejemplo de la vida cotidiana para que quede claro:
- El ID del empleado determina su código postal:
id_empleado → codigo_postal - El código postal determina la ciudad:
codigo_postal → ciudad - Por tanto:
id_empleado → codigo_postal → ciudad
La ciudad depende transitivamente del empleado a través del código postal. La ciudad no describe directamente al empleado: describe al código postal. Eso es exactamente lo que la 3FN no permite.
Definición formal de la Tercera Forma Normal
Una tabla está en Tercera Forma Normal cuando cumple estas condiciones:
- Ya está en Segunda Forma Normal.
- Ningún atributo que no sea parte de la clave primaria depende de otro atributo que tampoco sea parte de la clave primaria.
Dicho de forma más directa: todos los atributos no clave deben depender directamente de la clave primaria, y solo de la clave primaria. No de otros atributos no clave.
Una regla mnemotécnica que se usa mucho para recordarlo es la frase de Bill Kent: "Every non-key attribute must depend on the key, the whole key, and nothing but the key". En español: cada atributo no clave debe depender de la clave, de toda la clave, y de nada más que la clave.
- "De la clave" → Primera Forma Normal: la clave existe y los valores son atómicos.
- "De toda la clave" → Segunda Forma Normal: no hay dependencias parciales.
- "De nada más que la clave" → Tercera Forma Normal: no hay dependencias transitivas.
Primer ejemplo: empleados con datos de ubicación
Veamos el ejemplo más clásico. Una tabla de empleados que incluye su ubicación:
Tabla empleados (viola la 3FN):
| id_empleado (PK) | nombre | departamento | salario | codigo_postal | ciudad | provincia |
|---|---|---|---|---|---|---|
| E001 | Laura Martín | Desarrollo | 42000 | 28001 | Madrid | Madrid |
| E002 | Carlos Ruiz | Diseño | 38000 | 08001 | Barcelona | Cataluña |
| E003 | Sofía Mora | Desarrollo | 45000 | 28001 | Madrid | Madrid |
| E004 | Pedro Díaz | Marketing | 36000 | 08001 | Barcelona | Cataluña |
Esta tabla ya está en 1FN (valores atómicos, clave primaria definida) y en 2FN (la clave es una sola columna, no hay dependencias parciales posibles). Pero hay un problema sutil.
Analicemos las dependencias:
id_empleado → nombre: el nombre depende directamente del empleado. ✅id_empleado → departamento: el departamento depende directamente del empleado. ✅id_empleado → salario: el salario depende directamente del empleado. ✅id_empleado → codigo_postal: el código postal depende directamente del empleado. ✅codigo_postal → ciudad: la ciudad depende del código postal, no del empleado. ⚠️codigo_postal → provincia: la provincia depende del código postal, no del empleado. ⚠️
La ciudad y la provincia dependen transitivamente del empleado a través del código postal:
id_empleado → codigo_postal → ciudad
id_empleado → codigo_postal → provinciaCiudad y provincia no describen al empleado directamente. Describen al código postal. Eso viola la 3FN.
Los problemas concretos
- Redundancia: Madrid y Cataluña aparecen repetidas en varias filas. Con miles de empleados, esa redundancia se multiplica.
- Anomalía de actualización: si el código postal 28001 cambia su nombre de ciudad (algo posible en casos de reorganización administrativa), hay que actualizar todas las filas de empleados con ese código postal. Si se olvida alguna, la base de datos tiene datos contradictorios.
- Anomalía de inserción: no puedes registrar que el código postal 41001 pertenece a Sevilla sin tener un empleado con ese código.
- Anomalía de eliminación: si eliminas todos los empleados con código postal 08001, pierdes la información de que ese código corresponde a Barcelona.
Cómo corregirlo: extraer la dependencia transitiva a su propia tabla
La solución es crear una tabla separada para los datos que dependen del código postal, y dejar en la tabla de empleados solo lo que depende directamente del empleado.
Tabla codigos_postales (nueva):
| codigo_postal (PK) | ciudad | provincia |
|---|---|---|
| 28001 | Madrid | Madrid |
| 08001 | Barcelona | Cataluña |
Tabla empleados (corregida):
| id_empleado (PK) | nombre | departamento | salario | codigo_postal (FK) |
|---|---|---|---|---|
| E001 | Laura Martín | Desarrollo | 42000 | 28001 |
| E002 | Carlos Ruiz | Diseño | 38000 | 08001 |
| E003 | Sofía Mora | Desarrollo | 45000 | 28001 |
| E004 | Pedro Díaz | Marketing | 36000 | 08001 |
Ahora ciudad y provincia se guardan una sola vez. Si el nombre de una ciudad cambia, se actualiza en un solo lugar. Puedes registrar códigos postales sin necesidad de tener empleados. Y si un empleado se borra, no se pierde la información geográfica.
Para obtener todos los datos juntos cuando se necesita, un JOIN es suficiente:
SELECT
e.nombre,
e.departamento,
e.salario,
cp.ciudad,
cp.provincia
FROM empleados e
JOIN codigos_postales cp ON cp.codigo_postal = e.codigo_postal;Segundo ejemplo: productos con datos del proveedor
Un catálogo de productos que incluye información del proveedor:
Tabla productos (viola la 3FN):
| id_producto (PK) | nombre | precio | id_proveedor | nombre_proveedor | pais_proveedor | contacto_proveedor |
|---|---|---|---|---|---|---|
| P001 | Laptop Pro | 1299.99 | V10 | TechSupply S.A. | España | ventas@techsupply.com |
| P002 | Mouse | 45.50 | V10 | TechSupply S.A. | España | ventas@techsupply.com |
| P003 | Teclado | 89.99 | V10 | TechSupply S.A. | España | ventas@techsupply.com |
| P004 | Monitor 27" | 349.99 | V11 | ScreenWorld | Alemania | info@screenworld.de |
| P005 | Hub USB | 34.99 | V11 | ScreenWorld | Alemania | info@screenworld.de |
Esta tabla está en 1FN y en 2FN (clave primaria simple). Pero analicemos las dependencias:
id_producto → nombre✅id_producto → precio✅id_producto → id_proveedor✅ El producto tiene un proveedor.id_proveedor → nombre_proveedor⚠️ El nombre del proveedor depende del proveedor, no del producto.id_proveedor → pais_proveedor⚠️ Ídem.id_proveedor → contacto_proveedor⚠️ Ídem.
La cadena transitiva es clara:
id_producto → id_proveedor → nombre_proveedor
id_producto → id_proveedor → pais_proveedor
id_producto → id_proveedor → contacto_proveedorTechSupply S.A. aparece tres veces, con España y su email repetidos en cada fila. Si cambian de email, hay que actualizar tres filas. Si añaden un producto nuevo de ese proveedor, los datos se vuelven a repetir.
Solución aplicando la 3FN
Tabla proveedores (nueva):
| id_proveedor (PK) | nombre | pais | contacto |
|---|---|---|---|
| V10 | TechSupply S.A. | España | ventas@techsupply.com |
| V11 | ScreenWorld | Alemania | info@screenworld.de |
Tabla productos (corregida):
| id_producto (PK) | nombre | precio | id_proveedor (FK) |
|---|---|---|---|
| P001 | Laptop Pro | 1299.99 | V10 |
| P002 | Mouse | 45.50 | V10 |
| P003 | Teclado | 89.99 | V10 |
| P004 | Monitor 27" | 349.99 | V11 |
| P005 | Hub USB | 34.99 | V11 |
Los datos de TechSupply ahora aparecen una sola vez. Si cambian su email, una actualización en un solo registro resuelve todo.
Tercer ejemplo: pedidos con datos del empleado que los gestiona
Un ejemplo con una cadena transitiva un poco más larga para que veas que el mismo principio se aplica sin importar cuántos escalones tenga:
Tabla pedidos (viola la 3FN):
| id_pedido (PK) | fecha | id_empleado | nombre_empleado | id_departamento | nombre_departamento | edificio |
|---|---|---|---|---|---|---|
| 1001 | 2024-06-01 | E001 | Laura Martín | D01 | Ventas | Edificio A |
| 1002 | 2024-06-03 | E001 | Laura Martín | D01 | Ventas | Edificio A |
| 1003 | 2024-06-05 | E002 | Carlos Ruiz | D01 | Ventas | Edificio A |
| 1004 | 2024-06-07 | E003 | Sofía Mora | D02 | Desarrollo | Edificio B |
Las dependencias transitivas aquí forman una cadena de dos eslabones:
id_pedido → id_empleado → nombre_empleado
id_pedido → id_empleado → id_departamento → nombre_departamento
id_pedido → id_empleado → id_departamento → edificioHay que crear una tabla para cada eslabón de la cadena:
Tabla departamentos:
| id_departamento (PK) | nombre | edificio |
|---|---|---|
| D01 | Ventas | Edificio A |
| D02 | Desarrollo | Edificio B |
Tabla empleados:
| id_empleado (PK) | nombre | id_departamento (FK) |
|---|---|---|
| E001 | Laura Martín | D01 |
| E002 | Carlos Ruiz | D01 |
| E003 | Sofía Mora | D02 |
Tabla pedidos (corregida):
| id_pedido (PK) | fecha | id_empleado (FK) |
|---|---|---|
| 1001 | 2024-06-01 | E001 |
| 1002 | 2024-06-03 | E001 |
| 1003 | 2024-06-05 | E002 |
| 1004 | 2024-06-07 | E003 |
Para consultar todos los datos juntos:
SELECT
p.id_pedido,
p.fecha,
e.nombre AS empleado,
d.nombre AS departamento,
d.edificio
FROM pedidos p
JOIN empleados e ON e.id_empleado = p.id_empleado
JOIN departamentos d ON d.id_departamento = e.id_departamento
ORDER BY p.fecha;Cuarto ejemplo: cursos con datos del instructor
Un caso de plataforma educativa para consolidar el concepto:
Tabla cursos (viola la 3FN):
| id_curso (PK) | titulo | duracion_horas | id_instructor | nombre_instructor | especialidad | email_instructor |
|---|---|---|---|---|---|---|
| C01 | Python Básico | 20 | I01 | Marta Sanz | Programación | marta@escuela.com |
| C02 | Python Avanzado | 30 | I01 | Marta Sanz | Programación | marta@escuela.com |
| C03 | Bases de Datos | 25 | I02 | Jorge Ríos | Datos | jorge@escuela.com |
| C04 | SQL Avanzado | 15 | I02 | Jorge Ríos | Datos | jorge@escuela.com |
Dependencias transitivas:
id_curso → id_instructor → nombre_instructor
id_curso → id_instructor → especialidad
id_curso → id_instructor → email_instructorMarta Sanz y sus datos aparecen repetidos en dos filas. Si cambia su email, hay que actualizarlo en múltiples lugares. Si se elimina su último curso, desaparecen todos sus datos.
Solución
Tabla instructores:
| id_instructor (PK) | nombre | especialidad | |
|---|---|---|---|
| I01 | Marta Sanz | Programación | marta@escuela.com |
| I02 | Jorge Ríos | Datos | jorge@escuela.com |
Tabla cursos (corregida):
| id_curso (PK) | titulo | duracion_horas | id_instructor (FK) |
|---|---|---|---|
| C01 | Python Básico | 20 | I01 |
| C02 | Python Avanzado | 30 | I01 |
| C03 | Bases de Datos | 25 | I02 |
| C04 | SQL Avanzado | 15 | I02 |
Cómo detectar una dependencia transitiva: el método de las preguntas
Para cualquier columna de tu tabla que no forme parte de la clave primaria, hazte estas dos preguntas en orden:
Pregunta 1: ¿Esta columna describe directamente a la entidad principal de la tabla?
- En una tabla de empleados: ¿el nombre del departamento describe al empleado? No, describe al departamento. ❌ Sospechosa.
- En una tabla de empleados: ¿el salario describe al empleado? Sí, el salario es una propiedad del empleado. ✅ Bien ubicada.
Pregunta 2: Si la respuesta anterior es "no", ¿hay otra columna no clave en la tabla que determine el valor de esta?
- ¿El código postal determina la ciudad? Sí. → Dependencia transitiva confirmada. La ciudad debe ir en otra tabla con el código postal como clave.
- ¿El ID del proveedor determina el nombre del proveedor? Sí. → Dependencia transitiva confirmada. El nombre del proveedor debe ir en la tabla de proveedores.
Diferencia clave entre 2FN y 3FN
Es común confundir ambas formas normales. La diferencia es la siguiente:
| Aspecto | Segunda Forma Normal | Tercera Forma Normal |
|---|---|---|
| Qué elimina | Dependencias parciales de la clave | Dependencias transitivas entre atributos no clave |
| Requiere | Clave primaria compuesta | Cualquier tipo de clave primaria |
| El problema es | Una columna depende de PARTE de la clave | Una columna depende de OTRA COLUMNA no clave |
| La solución es | Separar en tablas según la parte de clave que determina cada grupo | Separar en tablas cada entidad que aparece como intermediaria |
Una forma de recordarlo:
- 2FN pregunta: ¿Depende de toda la clave o solo de parte de ella?
- 3FN pregunta: ¿Depende de la clave directamente o pasa por otro atributo no clave?
El esquema completo en SQL aplicando 1FN, 2FN y 3FN
Para cerrar, veamos cómo quedaría un sistema de gestión de pedidos que aplica correctamente las tres formas normales:
-- 3FN: cada entidad en su propia tabla
-- Los datos de una entidad solo viven en un lugar
CREATE TABLE provincias (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL
);
CREATE TABLE ciudades (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
id_provincia INT NOT NULL,
FOREIGN KEY (id_provincia) REFERENCES provincias(id)
);
CREATE TABLE codigos_postales (
codigo CHAR(5) PRIMARY KEY,
id_ciudad INT NOT NULL,
FOREIGN KEY (id_ciudad) REFERENCES ciudades(id)
);
CREATE TABLE clientes (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
codigo_postal CHAR(5),
FOREIGN KEY (codigo_postal) REFERENCES codigos_postales(codigo)
);
CREATE TABLE categorias (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(50) NOT NULL
);
CREATE TABLE proveedores (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
pais VARCHAR(50),
contacto VARCHAR(100)
);
CREATE TABLE productos (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
precio DECIMAL(10,2) NOT NULL,
stock INT DEFAULT 0,
id_categoria INT,
id_proveedor INT,
FOREIGN KEY (id_categoria) REFERENCES categorias(id),
FOREIGN KEY (id_proveedor) REFERENCES proveedores(id)
);
CREATE TABLE pedidos (
id INT AUTO_INCREMENT PRIMARY KEY,
id_cliente INT NOT NULL,
fecha DATE NOT NULL,
estado VARCHAR(20) DEFAULT 'pendiente',
FOREIGN KEY (id_cliente) REFERENCES clientes(id)
);
-- Tabla de unión: solo lo que depende de (id_pedido, id_producto) juntos
CREATE TABLE detalle_pedidos (
id_pedido INT NOT NULL,
id_producto INT NOT NULL,
cantidad INT NOT NULL,
precio_unit DECIMAL(10,2) NOT NULL,
PRIMARY KEY (id_pedido, id_producto),
FOREIGN KEY (id_pedido) REFERENCES pedidos(id),
FOREIGN KEY (id_producto) REFERENCES productos(id)
);Cada tabla tiene exactamente los datos que le corresponden. No hay redundancias, no hay dependencias parciales, no hay dependencias transitivas. El esquema está en 3FN.
Lista de verificación para la 3FN
Antes de dar una tabla por válida respecto a la 3FN, hazte estas preguntas sobre cada columna que no forma parte de la clave:
- ¿Esta columna describe directamente a la entidad principal que representa la tabla?
- ¿Hay otra columna no clave de esta misma tabla que determine el valor de esta columna?
- Si cambias el valor de esta columna en una fila, ¿tendrías que cambiarlo en otras filas también para mantener la consistencia?
- ¿El mismo valor de esta columna aparece repetido en varias filas siempre que aparece el mismo valor de otra columna no clave?
Si alguna de las últimas tres respuestas es sí, hay una dependencia transitiva que resolver.
Conclusión
La Tercera Forma Normal completa el proceso de normalización básica. Con ella eliminas el último tipo de redundancia que las dos formas anteriores no detectan: los datos que dependen de otro dato no clave en lugar de depender directamente de la clave primaria.
Las tres formas normales juntas forman un sistema coherente:
- 1FN: valores atómicos, sin grupos repetidos.
- 2FN: cada columna depende de toda la clave, no solo de una parte.
- 3FN: cada columna depende directamente de la clave, sin pasar por otra columna no clave.
Aplicar las tres de forma sistemática te garantiza un diseño libre de redundancias innecesarias, libre de anomalías de actualización, inserción y eliminación, y preparado para crecer de forma limpia y predecible.
Para afianzar los conceptos te recomendamos releer nuestros artículos sobre la Primera Forma Normal con ejemplos claros y sobre la Segunda Forma Normal explicada paso a paso, que forman junto a este artículo una trilogía completa sobre normalización.
No hay comentarios todavía. Sé el primero en compartir tu opinión.