La normalización es el proceso de organizar una base de datos para eliminar redundancia y dependencias problemáticas. Una tabla mal diseñada puede corromperse sola con el tiempo — la normalización previene eso con reglas matemáticas concretas.
El problema sin normalización
Imagina una tabla que guarda pedidos con toda la información del cliente en cada fila:
| pedido_id | cliente_nombre | cliente_email | cliente_ciudad | producto | precio |
|-----------|----------------|-------------------|----------------|---------------|--------|
| 1 | Juan Pérez | juan@ej.com | Guadalajara | Cable HDMI | 150.00 |
| 2 | Juan Pérez | juan@ej.com | Guadalajara | Mouse USB | 200.00 |
| 3 | Ana García | ana@ej.com | CDMX | Teclado | 350.00 |
Problemas evidentes:
- Anomalía de actualización: Si Juan cambia su email, hay que actualizar múltiples filas
- Anomalía de inserción: No puedo agregar un cliente sin que tenga un pedido
- Anomalía de eliminación: Si elimino el único pedido de Ana, pierdo sus datos como cliente
- Redundancia: El nombre y ciudad de Juan se repite N veces
Primera Forma Normal (1NF)
Regla: cada celda contiene un valor atómico (indivisible) y no hay grupos repetidos.
-- MAL: la columna "telefonos" tiene múltiples valores
| cliente_id | nombre | telefonos |
|------------|------------|------------------------|
| 1 | Juan Pérez | 333-1234, 333-5678 |
-- BIEN: tabla separada para teléfonos
CREATE TABLE clientes (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(100) NOT NULL,
email VARCHAR(150) UNIQUE NOT NULL
);
CREATE TABLE telefonos_clientes (
id INT PRIMARY KEY AUTO_INCREMENT,
cliente_id INT NOT NULL,
telefono VARCHAR(20) NOT NULL,
tipo ENUM('celular', 'fijo', 'trabajo') DEFAULT 'celular',
FOREIGN KEY (cliente_id) REFERENCES clientes(id) ON DELETE CASCADE
);
Segunda Forma Normal (2NF)
Regla: debe estar en 1NF y todos los atributos no-clave deben depender de la clave primaria completa (aplica cuando la clave primaria es compuesta).
-- MAL: tabla de inscripciones donde precio_materia depende solo de materia_id
-- (no de la clave compuesta alumno_id + materia_id)
CREATE TABLE inscripciones_mal (
alumno_id INT,
materia_id INT,
calificacion DECIMAL(4,2),
precio_materia DECIMAL(10,2), -- ← depende solo de materia_id, no del par
PRIMARY KEY (alumno_id, materia_id)
);
-- BIEN: precio_materia va en su propia tabla
CREATE TABLE materias (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(100) NOT NULL,
precio DECIMAL(10,2) NOT NULL
);
CREATE TABLE inscripciones (
alumno_id INT,
materia_id INT,
calificacion DECIMAL(4,2),
PRIMARY KEY (alumno_id, materia_id),
FOREIGN KEY (alumno_id) REFERENCES alumnos(id),
FOREIGN KEY (materia_id) REFERENCES materias(id)
);
Tercera Forma Normal (3NF)
Regla: debe estar en 2NF y ningún atributo no-clave debe depender de otro atributo no-clave (sin dependencias transitivas).
-- MAL: ciudad_cp depende de codigo_postal, no de cliente_id
CREATE TABLE clientes_mal (
id INT PRIMARY KEY,
nombre VARCHAR(100),
codigo_postal VARCHAR(10),
ciudad_cp VARCHAR(100) -- ← depende de codigo_postal, no del id
);
-- BIEN: extraer la dependencia transitiva
CREATE TABLE codigos_postales (
codigo_postal VARCHAR(10) PRIMARY KEY,
ciudad VARCHAR(100) NOT NULL,
estado VARCHAR(100) NOT NULL
);
CREATE TABLE clientes (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(100) NOT NULL,
codigo_postal VARCHAR(10),
FOREIGN KEY (codigo_postal) REFERENCES codigos_postales(codigo_postal)
);
¿Cuándo desnormalizar?
La normalización completa puede generar muchos JOINs y afectar el rendimiento en consultas de lectura masiva. A veces se desnormaliza intencionalmente por rendimiento:
- Reportes y analytics: tablas desnormalizadas (data warehouses)
- Caché de contadores: guardar el total de vistas en la tabla del artículo en lugar de contar cada vez
- Datos históricos: guardar el precio al momento de la venta aunque el precio actual cambie
"Normalizar una base de datos es como ordenar tu código — no es opcional si quieres que el sistema sobreviva al crecimiento."