Skip to content

2.15 Normalización

El modelado de datos es el proceso de definir la estructura y organización de los datos en una base de datos relacional eficiente, no redundante y fácil de mantener. Implica identificar las entidades, sus atributos y las relaciones entre ellas, así como aplicar principios de normalización para optimizar el diseño de la base de datos.

¿Qué es el modelado de datos?

El modelado de datos implica identificar las entidades (tablas), sus atributos (columnas) y las relaciones entre ellas. Un modelo de datos bien diseñado facilita la comprensión de los datos, mejora el rendimiento de las consultas y asegura que se cumplan las reglas de negocio.

Entidades y atributos

Las entidades son objetos o conceptos del mundo real que queremos representar en la base de datos. Cada entidad se convierte en una tabla, y sus atributos se convierten en columnas de esa tabla.

Por ejemplo, en un sistema de gestión de pedidos, podríamos tener entidades como "Clientes", "Pedidos" y "Productos". Cada una de estas entidades tendría sus propios atributos:

  • Clientes: ID, Nombre, Email, Dirección
  • Pedidos: ID, Cliente_ID, Producto, Cantidad, Fecha
  • Productos: ID, Nombre, Precio, Stock

Relaciones entre entidades

Las relaciones entre entidades definen cómo se conectan las tablas entre sí. Existen tres tipos principales de relaciones:

  • Uno a uno (1:1): Cada registro en una tabla se relaciona con un único registro en otra tabla. Por ejemplo, un usuario puede tener un único perfil asociado.
  • Uno a muchos (1:N): Un registro en una tabla puede estar relacionado con múltiples registros en otra tabla. Por ejemplo, un cliente puede tener múltiples pedidos.
  • Muchos a muchos (N:M): Los registros en una tabla pueden estar relacionados con múltiples registros en otra tabla y viceversa. Por ejemplo, un producto puede estar en múltiples pedidos, y un pedido puede contener múltiples productos.

Claves primarias y foráneas

Las claves primarias son atributos que identifican de manera única cada registro en una tabla. Por ejemplo, el ID de un cliente o el ID de un pedido o producto.

Las claves foráneas son atributos que establecen una relación entre dos tablas, apuntando a la clave primaria de otra tabla. Por ejemplo, en la tabla "Pedidos", el atributo Cliente_ID sería una clave foránea que hace referencia al ID de la tabla "Clientes".

En esta explicación hablaremos de atributos clave para referirnos tanto a claves primarias como foráneas, y de atributos no clave para referirnos a los demás atributos de una tabla.

Normalización

La normalización es el proceso de organizar los datos en tablas para reducir la redundancia y mejorar la integridad de los datos. Implica dividir las tablas en entidades más pequeñas y establecer relaciones entre ellas.

Formas normales

Existen varias formas normales, cada una con sus propias reglas para organizar los datos:

  • Primera forma normal (1NF): Elimina los grupos repetitivos y asegura que cada columna contenga valores atómicos.
  • Segunda forma normal (2NF): Elimina la dependencia parcial, asegurando que todos los atributos dependen completamente de la clave primaria.
  • Tercera forma normal (3NF): Elimina la dependencia transitiva, asegurando que los atributos no clave no dependen de otros atributos no clave.
  • Forma normal de Boyce-Codd (BCNF): Una versión más estricta de la tercera forma normal que elimina todas las dependencias funcionales no triviales.

Hay formas normales adicionales (4NF, 5NF, etc.), pero en la práctica, la mayoría de las bases de datos se normalizan hasta la 3NF o BCNF.

Advertencia sobre la normalización

Aunque puede sonar muy complicado, la normalización en la vida real no es tan difícil como parece. En la mayoría de los casos, con aplicar las tres primeras formas normales (1NF, 2NF y 3NF) es suficiente para tener una base de datos bien estructurada.

Si tu base de datos no usa claves primarias compuestas, y tus claves primarias son autoincrementales, es muy probable que tu base de datos ya esté en 3NF sin que te hayas dado cuenta.

Esto no siempre es posible, hay tablas que por su naturaleza no pueden ser normalizadas más allá de cierto punto sin perder eficiencia o funcionalidad. En esos casos, es importante evaluar los trade-offs entre normalización y rendimiento según las necesidades específicas de la aplicación.

1NF (Primera Forma Normal)

Una tabla está en 1NF si cumple con las siguientes condiciones:

  • Todos los atributos contienen valores atómicos (indivisibles), es decir, no hay listas o conjuntos de valores en una sola columna.
  • Cada columna contiene valores del mismo tipo de datos.

Veamos una tabla que no cumple con la 1NF:

CREATE TABLE movies (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    titulo TEXT,
    generos TEXT
);
INSERT INTO movies (titulo, generos) VALUES
('Inception', 'Sci-Fi, Action'),
('The Dark Knight', 'Action, Drama'),
('Interstellar', 'Sci-Fi, Drama');

Esta tabla presenta varios problemas que violan la 1NF:

  1. La columna generos contiene múltiples valores separados por comas, lo que significa que no es atómica. Esto provocará dificultades al intentar realizar consultas o análisis basados en géneros específicos, ¿cómo buscar todas las películas de "Action" si los géneros están almacenados como una cadena de texto? Podríamos usar LIKE, pero esto no es eficiente ni escalable a largo plazo.
  2. No hay una clave primaria definida, lo que lleva a inconsistencias en los datos.
  3. La columna id no tiene un tipo de dato definido, lo que puede causar inconsistencias en los datos almacenados y dificultar las operaciones de comparación o búsqueda.

Para normalizar esta tabla a 1NF, debemos asegurarnos de que cada columna contenga valores atómicos y definir una clave primaria adecuada. Podemos crear una tabla separada para los géneros y establecer una relación entre las películas y sus géneros:

DROP TABLE IF EXISTS movies;
DROP TABLE IF EXISTS genres;
DROP TABLE IF EXISTS movie_genres;
CREATE TABLE movies (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    titulo TEXT
);

CREATE TABLE genres (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    nombre TEXT
);

CREATE TABLE movie_genres (
    movie_id INTEGER,
    genre_id INTEGER,
    FOREIGN KEY (movie_id) REFERENCES movies(id),
    FOREIGN KEY (genre_id) REFERENCES genres(id),
    PRIMARY KEY (movie_id, genre_id)
);

INSERT INTO movies (titulo) VALUES
('Inglorious Basterds'),
('The end of Evangelion'),
('Interstellar');
INSERT INTO genres (nombre) VALUES
('Sci-Fi'),
('Action'),
('Drama'),
('Animation');

INSERT INTO movie_genres (movie_id, genre_id) VALUES
(
(SELECT id FROM movies WHERE titulo = 'Inglorious Basterds'),
(SELECT id FROM genres WHERE nombre = 'Action')
),
(
(SELECT id FROM movies WHERE titulo = 'Inglorious Basterds'),
(SELECT id FROM genres WHERE nombre = 'Drama')
),
(
(SELECT id FROM movies WHERE titulo = 'The end of Evangelion'),
(SELECT id FROM genres WHERE nombre = 'Action')
),
(
(SELECT id FROM movies WHERE titulo = 'The end of Evangelion'),
(SELECT id FROM genres WHERE nombre = 'Animation')
),
(
(SELECT id FROM movies WHERE titulo = 'Interstellar'),
(SELECT id FROM genres WHERE nombre = 'Sci-Fi'),
),
(
(SELECT id FROM movies WHERE titulo = 'Interstellar'),
(SELECT id FROM genres WHERE nombre = 'Drama')
);

Ahora, cada género está representado como un registro separado en la tabla genres, y la tabla movie_genres establece una relación muchos a muchos entre películas y géneros. Esto cumple con la 1NF al garantizar que todos los atributos contienen valores atómicos y que cada columna contiene valores del mismo tipo de datos.

Ahora la tabla está en 1NF, ya que cada columna contiene valores atómicos y del mismo tipo de datos, y hemos definido claves primarias adecuadas para cada tabla.

Además, gracias a esta estructura, ahora podemos realizar consultas más eficientes y precisas, como buscar todas las películas de un género específico o listar todos los géneros asociados a una película determinada.

Esta tabla, además, ya cumple con la 2NF y la 3NF!

2NF (Segunda Forma Normal)

La segunda forma normal (2NF) se basa en la 1NF y añade la condición de que todos los atributos no clave deben depender completamente de la clave primaria.

Para esto:

  • La tabla debe estar en 1NF.
  • No debe haber dependencias parciales, es decir, ningún atributo no clave debe depender solo de una parte de una clave primaria compuesta.

Si tu tabla tiene una clave primaria simple (no compuesta), entonces automáticamente está en 2NF si ya está en 1NF, ya que no puede haber dependencias parciales en ese caso.

Partiendo del ejemplo anterior, imagina que nuestra tabla intermedia fuera asi:

CREATE TABLE movie_genres (
    movie_id INTEGER,
    genre_id INTEGER,
    titulo_pelicula TEXT,
    nombre_genero TEXT,
    PRIMARY KEY (movie_id, genre_id)
);

Quizá alguien pensó al diseñarla que está bien tener los nombres de las películas y los géneros en la tabla intermedia para facilitar las consultas, pero esto viola la 2NF, ya que titulo_pelicula depende solo de movie_id y nombre_genero depende solo de genre_id. Esto crea redundancia y problemas de mantenimiento.

Una regla sencilla a seguir es usar claves simples siempre que sea posible, y usar claves compuestas cuando hagamos tablas intermedias para relaciones muchos a muchos. No siempre podrás seguir esta regla dependiendo de la naturaleza de los datos, pero en el 90% de los casos te salvará de problemas de normalización.

Esta tabla, además, ya cumple con la 3NF, ya que no existen dependencias transitivas entre los atributos no clave.

3NF (Tercera Forma Normal)

La tercera forma normal (3NF) se basa en la 2NF y añade la condición de que no debe haber dependencias transitivas entre los atributos no clave.

Debemos cumplir con lo siguiente:

  • La tabla debe estar en 2NF.
  • Ningún atributo no clave debe depender de otro atributo no clave.

Veamos un ejemplo de una tabla que cumple con 2NF pero no con 3NF:

CREATE TABLE ciudades (
    ciudad_id INTEGER PRIMARY KEY AUTOINCREMENT,
    nombre TEXT
);

CREATE TABLE clientes (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    nombre TEXT,
    ciudad_id INTEGER,
    FOREIGN KEY (city_id) REFERENCES cities(city_id)
);

En esta tabla, ciudad_id es una clave foránea que hace referencia a la tabla ciudades. Sin embargo, el atributo nombre del cliente no depende directamente de la clave primaria id, sino que depende de ciudad_id, lo que crea una dependencia transitiva. Esto viola la 3NF.

3NF es muy similar a 2NF, pero se da en tablas que tienen claves primarias simples, y en las que los atributos no clave dependen de otros atributos no clave. En la mayoría de los casos, si tu tabla tiene una clave primaria simple y no hay dependencias transitivas entre los atributos no clave, entonces tu tabla ya está en 3NF.

Resumiendo

Evita la redundancia de datos y mejora la integridad de tu base de datos aplicando los principios de normalización. Asegúrate de que tus tablas estén en al menos 3NF para garantizar un diseño eficiente y fácil de mantener. Recuerda que la normalización es una herramienta para mejorar el diseño de tu base de datos, pero no es una regla estricta que debes seguir al pie de la letra en todos los casos. Evalúa siempre las necesidades específicas de tu aplicación y encuentra el equilibrio adecuado entre normalización, rendimiento y funcionalidad.