Mantén SQLtest vivo: Ayuda a cubrir los costos del servidor de este mes de $100 para mantener todas las lecciones y el playground de SQL en línea. Apoya el proyecto →
Código SQL copiado al portapapeles

Lección 9.6 · Tiempo de lectura: ~10 min

La normalización de bases de datos es una forma de diseñar las tablas para que cada hecho se guarde en un solo lugar. Una base normalizada reparte los datos en tablas relacionadas mediante claves, lo que elimina los duplicados y los errores que provocan. En esta lección conocerás las tres primeras formas normales (1FN, 2FN, 3FN) y la FNBC, verás cómo dividir una tabla ancha con SQL y sabrás cuándo duplicar datos a propósito es la mejor opción.

Normalización de bases de datos: 1FN, 2FN y 3FN

En las lecciones anteriores de este módulo creaste tablas, las modificaste y construiste vistas sobre ellas. Queda una pregunta: qué tablas crear desde el principio, qué va en una misma tabla y qué merece una tabla propia.

La normalización responde a esa pregunta con unas pocas reglas formales llamadas formas normales. Sakila es una base bien normalizada, así que sus tablas sirven de ejemplo a lo largo de la lección.

Normalización: una tabla ancha con datos repetidos de clientes y películas se divide en las tablas customer, film y rental unidas por claves; la 1FN elimina las listas, la 2FN las dependencias parciales y la 3FN las transitivas

¿Qué es la normalización de bases de datos?

La normalización es el proceso de organizar columnas y tablas de modo que:

  • cada tabla describa un solo tipo de entidad: un cliente, una película, un alquiler;
  • cada hecho se guarde una vez y las demás tablas lo referencien por clave;
  • cada columna no clave dependa de la clave de su tabla, y solo de ella.

Las reglas se agrupan en formas normales: 1FN, 2FN, 3FN, FNBC (forma normal de Boyce-Codd) y superiores. Cada forma incluye la anterior. En la práctica, un esquema en tercera forma normal (3FN) se considera normalizado; las formas superiores tratan casos poco frecuentes.


Por qué normalizar: anomalías de datos

Imagina que los alquileres se guardan en una sola tabla ancha:

rental_idrental_datecustomer_emailcustomer_namecustomer_cityfilm_titlefilm_actors
12026-05-01mary@example.orgMARY SMITHSaseboACADEMY DINOSAURPENELOPE GUINESS, CHRISTIAN GABLE
22026-05-02mary@example.orgMARY SMITHSaseboACE GOLDFINGERBOB FAWCETT, MINNIE ZELLWEGER
32026-05-02patricia@example.orgPATRICIA JOHNSONSan BernardinoACADEMY DINOSAURPENELOPE GUINESS, CHRISTIAN GABLE

La tabla es fácil de leer, pero los datos del cliente y de la película se repiten en cada alquiler. Esto provoca tres tipos de problemas, llamados anomalías:

  • Anomalía de actualización. Mary se muda a otra ciudad. Hay que actualizar todos sus alquileres; si se olvida una fila, la base tendrá dos ciudades distintas para el mismo cliente.
  • Anomalía de inserción. No se puede añadir una película nueva hasta que alguien la alquile, porque cada fila es un alquiler.
  • Anomalía de borrado. Al borrar el único alquiler de Patricia se borra también todo lo que se sabe de Patricia.

La normalización elimina estas anomalías llevando cada hecho a la tabla a la que pertenece.


Primera forma normal (1FN)

Una tabla está en primera forma normal cuando:

  • cada columna guarda un solo valor (atómico), no una lista;
  • no hay grupos de columnas repetidas como phone1, phone2, phone3;
  • cada fila se puede identificar por una clave primaria.

La columna film_actors de arriba incumple la 1FN: guarda una lista de nombres en un solo valor. Una columna así es difícil de consultar. Para encontrar las películas con Penelope Guiness hay que buscar dentro del texto con LIKE, que no usa índices y encuentra por error nombres parecidos. Contar películas por actor es aún más difícil.

Sakila tiene un ejemplo real de este compromiso. En MySQL, la columna film.special_features es de tipo SET y guarda valores como Trailers,Commentaries:

SELECT special_features, COUNT(*) AS films
FROM film
GROUP BY special_features
ORDER BY films DESC
LIMIT 3;

Resultado: 15 combinaciones de cuatro extras, empezando por Trailers,Commentaries,Behind the Scenes (79 películas). Para contar películas por cada extra hay que partir estas cadenas.

La solución de la 1FN es una tabla aparte con una fila por valor. Es justo lo que hace Sakila con los actores: la tabla film_actor guarda un par (actor_id, film_id) por fila.

SELECT a.first_name, a.last_name, COUNT(*) AS films
FROM film_actor AS fa
JOIN actor AS a ON a.actor_id = fa.actor_id
GROUP BY a.actor_id, a.first_name, a.last_name
ORDER BY films DESC
LIMIT 3;

Resultado: los actores con más películas; GINA DEGENERES encabeza la lista con 42. Con una lista en una columna de texto, esta consulta sería poco práctica.


Segunda forma normal (2FN)

Una tabla está en segunda forma normal cuando está en 1FN y cada columna no clave depende de toda la clave primaria, no solo de una parte.

La 2FN solo importa en tablas con clave compuesta de varias columnas. Supongamos que film_actor también guardara nombres:

actor_idfilm_idactor_namefilm_title
11PENELOPE GUINESSACADEMY DINOSAUR
123PENELOPE GUINESSANACONDA CONFESSIONS

La clave es (actor_id, film_id), pero actor_name depende solo de actor_id y film_title solo de film_id. Es una dependencia parcial: el nombre del actor se repite en cada una de sus películas.

La solución es mover cada columna a la tabla de cuya clave depende: los nombres a actor y los títulos a film. La tabla film_actor real guarda solo las dos claves y last_update.


Tercera forma normal (3FN)

Una tabla está en tercera forma normal cuando está en 2FN y ninguna columna no clave depende de otra columna no clave. Una cadena así, clave → columna A → columna B, se llama dependencia transitiva.

Por ejemplo, si la tabla address guardara el país:

address_idaddresscity_idcitycountry
51913 Hanoi Way463SaseboJapan
61121 Loja Avenue449San BernardinoUnited States

city y country dependen de city_id, no de address_id. Cada dirección de la misma ciudad repite el nombre de la ciudad y el país, y cambiar el nombre de un país obligaría a actualizar miles de direcciones.

Sakila lo resuelve con una cadena de tablas: address guarda city_id, city guarda country_id y solo country guarda el nombre. Cada nombre se guarda una vez y la consulta los reúne con JOIN:

SELECT co.country, COUNT(*) AS customers
FROM customer AS c
JOIN address AS a ON a.address_id = c.address_id
JOIN city AS ci ON ci.city_id = a.city_id
JOIN country AS co ON co.country_id = ci.country_id
GROUP BY co.country
ORDER BY customers DESC
LIMIT 5;

Resultado: India (60), China (53), United States (36), Japan (31) y Mexico (30). Los datos normalizados se leen con joins: es el precio de guardar cada hecho una sola vez.

Una forma corta de recordar de la 1FN a la 3FN: cada columna no clave debe depender de la clave, de toda la clave y de nada más que la clave.

Forma normal de Boyce-Codd (FNBC)

La FNBC (BCNF) es una versión más estricta de la 3FN: toda columna que determina otra columna debe ser una clave candidata. La diferencia solo aparece en tablas con varias claves candidatas que se solapan, algo poco común. La mayoría de las tablas en 3FN ya están en FNBC. La cuarta y la quinta formas normales tratan de dependencias multivaluadas y casi no se discuten fuera de la teoría de bases de datos.


Cómo normalizar una tabla con SQL

Vamos a dividir la tabla ancha del principio, guardada como rental_flat, en tablas normalizadas. Ejecuta estas sentencias en tu propia base: las bases de ejemplo de SQLtest son de solo lectura. Primero, la nueva estructura:

CREATE TABLE customer_n (
    customer_id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(50) NOT NULL UNIQUE,
    name VARCHAR(90) NOT NULL,
    city VARCHAR(50) NOT NULL
);

CREATE TABLE film_n (
    film_id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL UNIQUE
);

CREATE TABLE rental_n (
    rental_id INT PRIMARY KEY,
    rental_date DATE NOT NULL,
    customer_id INT NOT NULL,
    film_id INT NOT NULL,
    FOREIGN KEY (customer_id) REFERENCES customer_n (customer_id),
    FOREIGN KEY (film_id) REFERENCES film_n (film_id)
);

Nota: esta es la sintaxis de MySQL. En PostgreSQL, sustituye INT AUTO_INCREMENT por INT GENERATED ALWAYS AS IDENTITY.

Después copiamos los datos. SELECT DISTINCT deja una fila por cliente y por película, y los alquileres encuentran sus claves con JOIN:

INSERT INTO customer_n (email, name, city)
SELECT DISTINCT customer_email, customer_name, customer_city
FROM rental_flat;

INSERT INTO film_n (title)
SELECT DISTINCT film_title
FROM rental_flat;

INSERT INTO rental_n (rental_id, rental_date, customer_id, film_id)
SELECT r.rental_id, r.rental_date, c.customer_id, f.film_id
FROM rental_flat AS r
JOIN customer_n AS c ON c.email = r.customer_email
JOIN film_n AS f ON f.title = r.film_title;

Resultado: 2 clientes, 2 películas y 3 alquileres. La ciudad de Mary ahora se guarda en una sola fila, así que un único UPDATE la cambia en todas partes.

Si SELECT DISTINCT devuelve dos filas con el mismo email, los datos ya son inconsistentes: el mismo cliente tiene nombres o ciudades distintos en distintos alquileres. La restricción UNIQUE sobre email rechazará la segunda fila y tendrás que decidir qué valor es el correcto. La normalización suele sacar a la luz problemas así.

La lista de actores pasa del mismo modo a un par de tablas, actor y film_actor. La ciudad seguiría el ejemplo de la 3FN: una tabla city aparte y una columna city_id en customer_n.


Desnormalización: cuándo se aceptan duplicados

La desnormalización consiste en guardar datos redundantes a propósito para que la lectura sea más rápida o más sencilla. Es un compromiso consciente, no un error de diseño, y la propia Sakila tiene algunos ejemplos:

  • payment guarda customer_id, aunque se puede obtener a través de rental_id. El duplicado ahorra un join en los informes de pagos; los dos valores coinciden en los 16 044 pagos vinculados a un alquiler.
  • La tabla film_text repite title y description de film para la búsqueda de texto completo.
  • La vista film_list devuelve los actores como una sola cadena separada por comas. Una vista no guarda nada, así que ofrece un formato ancho cómodo sin las anomalías.

Motivos habituales para desnormalizar:

  • informes y dashboards que unen muchas tablas grandes en cada petición;
  • contadores y totales, como el importe total de pagos de un cliente, costosos de recalcular;
  • almacenes de datos con esquema en estrella, que se cargan por lotes y rara vez se actualizan.

La regla práctica: diseña primero un esquema normalizado y desnormaliza solo los puntos concretos donde hayas medido un problema de rendimiento. Cada duplicado necesita una forma de mantenerse sincronizado: un trigger, una tarea programada o el código de la aplicación.


Conclusiones clave:

  • La normalización guarda cada hecho una vez y une las tablas por claves; evita las anomalías de actualización, inserción y borrado.
  • 1FN: un valor por columna, sin listas ni grupos de columnas repetidas, una clave primaria en cada tabla.
  • 2FN: ninguna columna depende de una parte de una clave compuesta.
  • 3FN: ninguna columna depende de otra columna no clave; "la clave, toda la clave y nada más que la clave".
  • Los datos normalizados se leen con JOIN; una vista puede mostrarlos en formato ancho.
  • Desnormaliza de forma deliberada y solo donde la lectura lo requiera, con un plan para mantener las copias sincronizadas.

Esta lección cierra el módulo sobre la creación y modificación de tablas. El siguiente módulo trata de escribir SQL legible y rápido: buenas prácticas, optimización de consultas e índices.


Preguntas frecuentes

¿Qué es la normalización de una base de datos?

La normalización es el proceso de organizar los datos en tablas relacionadas para que cada hecho se guarde una sola vez. Elimina los datos duplicados y las anomalías que provocan, como que un mismo cliente tenga dos direcciones distintas en filas diferentes.

¿Qué diferencia hay entre 1FN, 2FN y 3FN?

La 1FN exige valores atómicos y una clave primaria. La 2FN además prohíbe columnas que dependen solo de una parte de una clave compuesta. La 3FN además prohíbe columnas que dependen de otras columnas no clave, como el nombre de un país guardado junto al ID de una ciudad.

¿Una base normalizada es siempre mejor?

Para datos que cambian, normalmente sí: mantiene los datos coherentes y el esquema flexible. Para informes y análisis, una desnormalización controlada puede hacer las lecturas mucho más rápidas. Empieza con un esquema normalizado y desnormaliza donde hayas medido una necesidad.

¿La normalización hace más lentas las consultas?

Las lecturas necesitan más joins, lo que cuesta algo de tiempo, pero con índices en las columnas clave los joins son rápidos. Las escrituras suelen ser más rápidas, porque cada cambio actualiza una fila en lugar de muchas copias.

Preguntas de entrevista

Explica las anomalías de actualización, inserción y borrado.

Una anomalía de actualización ocurre cuando un hecho se guarda en varias filas y una actualización se salta algunas. Una anomalía de inserción ocurre cuando no se puede añadir un hecho sin datos ajenos, como una película sin alquiler. Una anomalía de borrado ocurre cuando borrar un hecho borra otro, como el único alquiler de un cliente que hace desaparecer al cliente.

¿Qué es una dependencia transitiva? Pon un ejemplo.

Es cuando una columna no clave depende de otra columna no clave en lugar de la clave. En address(address_id, city_id, city_name), city_name depende de city_id, no de address_id. La solución es una tabla city aparte, con lo que el esquema queda en 3FN.

¿Cuándo desnormalizarías un esquema?

Cuando las lecturas son mucho más frecuentes que las escrituras y los joins se han convertido en un cuello de botella medido, por ejemplo en informes, dashboards y almacenes de datos. Guarda los datos redundantes con una forma clara de sincronizarlos, como un trigger o una actualización programada.