Pregunta 1El teléfono de un cliente está duplicado en varias filas de pedido, así que cambiar el teléfono requiere actualizar cada fila sin saltarse ninguna — de lo contrario, los valores no concuerdan. ¿Cómo se llama este problema?
Diseño de tablas y normalización (esquema de 3 capas)
Usa orders_flat con columnas repetidas item1/item2 para sentir de primera mano las anomalías de actualización, inserción y eliminación, y luego divide la tabla paso a paso pasando por 1NF / 2NF / 3NF hasta llegar al esquema de 3 capas customer / order_record / order_line.
El dataset de este artículo — orders_flat (un ejemplo sin normalizar)
La normalización (una técnica de diseño que divide tablas para reducir datos duplicados e inconsistentes) es la idea de descomponer una única tabla gigante que lo contiene todo en varias tablas más fáciles de mantener consistentes.
En este artículo verás los problemas que ocurren en una tabla sin normalizar con columnas repetidas, y luego construirás una comprensión de 1NF / 2NF / 3NF y del esquema de 3 capas mediante ejercicios prácticos de diseño.
El dataset es orders_flat (2 filas), que mete los datos de pedidos en una sola tabla.
Es una tabla clásica sin normalizar donde el nombre del cliente, el teléfono, el producto 1 y el producto 2 están todos alineados en una sola fila — y la normalizarás en las tres tablas customer / order_record / order_line.
Tres anomalías en una tabla sin normalizar
Un diseño como orders_flat, que mete todo en una sola tabla, produce contradicciones fácilmente cuando añades, cambias o eliminas datos.
Los ejemplos clásicos son tres: anomalía de actualización / anomalía de inserción / anomalía de eliminación.
Una anomalía de actualización ocurre cuando el mismo dato se duplica por muchas filas — por ejemplo, si quieres cambiar un número de teléfono, tienes que actualizar todas las filas sin saltarte ninguna.
Una anomalía de inserción es cuando no puedes registrar un cliente que aún no ha hecho ningún pedido, porque no hay ninguna fila de pedido a la que pegar la información del cliente.
Una anomalía de eliminación es cuando eliminar un solo pedido también borra información que querías conservar, como el teléfono del cliente.
Esto ocurre porque el diseño no mantiene "un dato en un solo sitio".
-- Ejemplo de anomalía de eliminación: eliminar el pedido 2 también borra la info de contacto de Bob
-- (observa el comportamiento en una tabla desechable flat_demo para que la orders_flat real no se toque)
DROP TABLE IF EXISTS flat_demo;
CREATE TABLE flat_demo(
order_id INTEGER, customer TEXT, customer_tel TEXT, product TEXT
);
INSERT INTO flat_demo VALUES
(1,'Alice','03-1111','Pen'),
(2,'Bob','03-2222','Clip');
-- Bob solo tiene un pedido. Eliminarlo también pierde el teléfono 03-2222 de Bob
DELETE FROM flat_demo WHERE order_id = 2;
SELECT * FROM flat_demo;
-- -> La info de contacto de Bob ya no está en la BD (anomalía de eliminación)
De 1NF a 3NF — divide paso a paso
Las tablas se organizan por etapas mediante normalización.
1NF — deshazte de las columnas repetidas
Elimina las columnas repetidas numeradas del estilo item1 / item2.
Antes — columnas repetidas item1 / item2
| order_id | customer | tel | item1 | item1_qty | item2 | item2_qty |
|---|---|---|---|---|---|---|
| 1 | Alice | 03-1111 | Pen | 2 | Note | 1 |
| 2 | Bob | 03-2222 | Clip | 5 | (vacío) | NULL |
Después — una fila por producto, con la clave primaria compuesta `(order_id, product)`
| order_id (PK) | customer | tel | product (PK) | qty |
|---|---|---|---|---|
| 1 | Alice | 03-1111 | Pen | 2 |
| 1 | Alice | 03-1111 | Note | 1 |
| 2 | Bob | 03-2222 | Clip | 5 |
2NF — mueve las columnas que dependen solo de una parte de la clave primaria a otra tabla
La tabla del paso anterior tiene la clave primaria compuesta (order_id, product). Pero fíjate bien: customer y tel están determinados solo por `order_id` (el mismo order_id siempre tiene el mismo cliente).
Mover esas columnas que dependen de solo una parte de la clave primaria (dependencia parcial) a una tabla aparte es 2NF. La divides en una cabecera de pedido order_header y los detalles del pedido order_line.
Antes — customer y tel duplicados en cada fila
| order_id (PK) | customer | tel | product (PK) | qty |
|---|---|---|---|---|
| 1 | Alice | 03-1111 | Pen | 2 |
| 1 | Alice | 03-1111 | Note | 1 |
| 2 | Bob | 03-2222 | Clip | 5 |
Después — dividido en 2 tablas
order_header (una fila por pedido)
| order_id (PK) | customer | tel |
|---|---|---|
| 1 | Alice | 03-1111 |
| 2 | Bob | 03-2222 |
order_line (clave primaria compuesta (order_id, product), donde order_id es clave foránea a order_header)
| order_id (PK,FK) | product (PK) | qty |
|---|---|---|
| 1 | Pen | 2 |
| 1 | Note | 1 |
| 2 | Clip | 5 |
3NF — mueve a otra tabla las columnas determinadas por columnas que no son clave
Mirando el order_header del paso anterior, tel no está determinado por order_id sino por `customer` (la misma Alice siempre tiene 03-1111).
Eliminar las dependencias que pasan por otra columna que no es clave en lugar de hacerlo directamente desde la clave primaria (dependencia transitiva: order_id -> customer -> tel) es de lo que va 3NF.
Extraes un maestro de cliente customer y dejas order_record conservando solo la clave foránea al cliente.
Antes — tel depende de customer
| order_id (PK) | customer | tel |
|---|---|---|
| 1 | Alice | 03-1111 |
| 2 | Bob | 03-2222 |
Después — dividido en 3 tablas
customer (maestro de cliente)
| customer_id (PK) | customer_name | customer_tel |
|---|---|---|
| 1 | Alice | 03-1111 |
| 2 | Bob | 03-2222 |
order_record (cabecera de pedido: referencia al cliente mediante clave foránea)
| order_id (PK) | customer_id (FK) |
|---|---|
| 1 | 1 |
| 2 | 2 |
order_line (detalles de pedido: igual que en el paso anterior)
| order_id (PK,FK) | product (PK) | qty |
|---|---|---|
| 1 | Pen | 2 |
| 1 | Note | 1 |
| 2 | Clip | 5 |
Una vez dividido hasta aquí, cada dato (el teléfono de un cliente, el vínculo entre pedido y cliente, la cantidad de un producto en un pedido) vive en un único lugar. Eso elimina las anomalías de actualización, inserción y eliminación.
El esquema de 3 capas y la forma final — customer / order_record / order_line
El esquema normalizado se puede organizar como tres capas con roles diferentes.
- Capa maestra: entidades que cambian con poca frecuencia y son referenciadas por otras, como
customer - Capa transaccional: tablas que registran eventos de negocio, como
order_record - Capa de detalle: tablas que contienen las filas de desglose de un evento, como
order_line
Al apuntar el lado que referencia hacia la maestra con una clave foránea, cada dato (como el teléfono de un cliente) acaba en un solo sitio.
La forma final son estas tres tablas:
customer(customer_id clave primaria, customer_name, customer_tel), order_record(order_id clave primaria, customer_id -> customer), order_line(order_id -> order_record, product, qty, clave primaria compuesta (order_id, product)).
El teléfono vive en una sola fila de customer, así que una actualización es de una sola fila; un cliente sin pedidos aún puede añadirse a customer (anomalía de inserción resuelta); y eliminar un pedido deja al cliente en customer (anomalía de eliminación resuelta).
-- Ventaja de la normalización (solo lectura): el teléfono de un cliente está en una sola fila de customer
-- En la forma orders_flat tendrías que arreglar todas las filas de pedido de Alice = anomalía de actualización
UPDATE customer SET customer_tel = '03-9999'
WHERE customer_name = 'Alice';
-- Arregla un solo punto y el nuevo número fluye a cada pedido en el JOIN
SELECT o.order_id, c.customer_name, c.customer_tel
FROM order_record o
JOIN customer c ON o.customer_id = c.customer_id
ORDER BY o.order_id;
Tips — Pon siempre una clave primaria en cada tabla
Hayas normalizado o no, diseña cada tabla con una clave primaria (`PRIMARY KEY`). Una clave primaria te da tres cosas:
- Identificación única de fila: dos filas no pueden tener la misma clave, así que UPDATE / DELETE apuntan de forma fiable a exactamente una fila mediante WHERE pk = ...
- Referenciable desde otras tablas: una clave foránea necesita una clave primaria (o UNIQUE) del otro lado. Es la base para enlazar las tablas que divides al normalizar
- Indexación automática: las columnas de clave primaria reciben un índice B-Tree de forma automática, acelerando las búsquedas por igualdad y los JOINs
Si ninguna columna individual es única por sí sola, usa una clave primaria compuesta como PRIMARY KEY (order_id, product). Saltártela con "ya la añadiré luego" hace indistinguibles las filas duplicadas y renuncia a las tres ventajas anteriores.
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2¿Qué operación lleva una tabla a 1NF?
Pregunta 3Después de normalizar, orders_flat se divide en las tres tablas customer / order_record / order_line. ¿Cómo recuperas la lista combinada original?