2.6 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. Este proceso es fundamental para garantizar que los datos se almacenen de manera eficiente, se puedan recuperar fácilmente y se mantenga la integridad de los mismos.
¿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 estudiante puede estar inscrito en múltiples cursos y un curso puede tener múltiples estudiantes. Para manejar este tipo de relaciones, se suele crear una tabla intermedia que contenga las claves primarias de ambas tablas relacionadas.
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".
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.
1NF (Primera Forma Normal)
DROP TABLE IF EXISTS pokemons;
CREATE TABLE pokemons (
id INTEGER PRIMARY KEY,
nombre TEXT NOT NULL,
tipos TEXT NOT NULL
);
INSERT INTO pokemons (nombre, tipos) VALUES ('Charmander', 'Fuego');
INSERT INTO pokemons (nombre, tipos) VALUES ('Squirtle', 'Agua');
INSERT INTO pokemons (nombre, tipos) VALUES ('Zapdos', 'Eléctrico, Volador');
El problema de esta tabla es claro, los tipos de Pokémon están almacenados en una sola columna como una cadena de texto, estaremos repitiendo datos, dificultando consultas y haciendo la base de datos más pesada sin necesidad.
2NF (Segunda Forma Normal)
En cuanto resolvemos el problema de la 1NF, podemos ver que los tipos de Pokémon están repetidos, por lo que podemos crear una tabla intermedia para resolverlo y estar en 2NF.
DROP TABLE IF EXISTS pokemon_tipos;
DROP TABLE IF EXISTS pokemons;
CREATE TABLE pokemons (
id INTEGER PRIMARY KEY,
nombre TEXT NOT NULL
);
CREATE TABLE pokemon_tipos (
pokemon_id INTEGER,
tipo TEXT NOT NULL,
FOREIGN KEY (pokemon_id) REFERENCES pokemons(id)
);
INSERT INTO pokemons (id, nombre) VALUES (1, 'Charmander');
INSERT INTO pokemons (id, nombre) VALUES (2, 'Squirtle');
INSERT INTO pokemons (id, nombre) VALUES (3, 'Zapdos');
INSERT INTO pokemon_tipos (pokemon_id, tipo) VALUES (1, 'Fuego');
INSERT INTO pokemon_tipos (pokemon_id, tipo) VALUES (2, 'Agua');
INSERT INTO pokemon_tipos (pokemon_id, tipo) VALUES (3, 'Eléctrico');
INSERT INTO pokemon_tipos (pokemon_id, tipo) VALUES (3, 'Volador');
3NF (Tercera Forma Normal)
Para alcanzar la tercera forma normal, debemos asegurarnos de que no existan dependencias transitivas. En nuestro ejemplo, no hay dependencias transitivas, ya que los tipos de Pokémon dependen únicamente del Pokémon y no de otros atributos.
DROP TABLE IF EXISTS pokemon_tipos;
DROP TABLE IF EXISTS tipos;
DROP TABLE IF EXISTS pokemons;
CREATE TABLE pokemons (
id INTEGER PRIMARY KEY,
nombre TEXT NOT NULL
);
CREATE TABLE tipos (
id INTEGER PRIMARY KEY,
nombre TEXT NOT NULL UNIQUE
);
CREATE TABLE pokemon_tipos (
pokemon_id INTEGER,
tipo_id INTEGER,
FOREIGN KEY (pokemon_id) REFERENCES pokemons(id),
FOREIGN KEY (tipo_id) REFERENCES tipos(id)
);
INSERT INTO pokemons (id, nombre) VALUES (1, 'Bulbasaur'),
(2, 'Ivysaur'),
(3, 'Venusaur'),
(4, 'Charmander'),
(5, 'Charmeleon'),
(6, 'Charizard'),
(7, 'Squirtle'),
(8, 'Wartortle'),
(9, 'Blastoise'),
(145, 'Zapdos');
INSERT INTO tipos (id, nombre) VALUES (1, 'Fuego'),
(2, 'Agua'),
(3, 'Eléctrico'),
(4, 'Volador'),
(5, 'Planta');
INSERT INTO pokemon_tipos (pokemon_id, tipo_id) VALUES ((SELECT id FROM pokemons WHERE nombre = 'Bulbasaur'), (SELECT id FROM tipos WHERE nombre = 'Planta'));
INSERT INTO pokemon_tipos (pokemon_id, tipo_id) VALUES (SELECT id FROM pokemons WHERE nombre = 'Ivysaur'), (SELECT id FROM tipos WHERE nombre = 'Planta'));
INSERT INTO pokemon_tipos (pokemon_id, tipo_id) VALUES (SELECT id FROM pokemons WHERE nombre = 'Venusaur'), (SELECT id FROM tipos WHERE nombre = 'Planta'));
INSERT INTO pokemon_tipos (pokemon_id, tipo_id) VALUES ((SELECT id FROM pokemons WHERE nombre = 'Charmander'), (SELECT id FROM tipos WHERE nombre = 'Fuego'));
INSERT INTO pokemon_tipos (pokemon_id, tipo_id) VALUES ((SELECT id FROM pokemons WHERE nombre = 'Charmeleon'), (SELECT id FROM tipos WHERE nombre = 'Fuego'));
INSERT INTO pokemon_tipos (pokemon_id, tipo_id) VALUES ((SELECT id FROM pokemons WHERE nombre = 'Charizard'), (SELECT id FROM tipos WHERE nombre = 'Fuego'));
INSERT INTO pokemon_tipos (pokemon_id, tipo_id) VALUES ((SELECT id FROM pokemons WHERE nombre = 'Charizard'), (SELECT id FROM tipos WHERE nombre = 'Volador'));