Kodokon kodokon.com

Modelado de datos: normalización y desnormalización

Estructura tus esquemas de 1NF a 3NF para eliminar anomalías, y aprende cuándo desnormalizar con conocimiento de causa.

10 min · 3 preguntas

Abrir esta lección en Kodokon

Un mal esquema te cuesta años. La normalización persigue una sola cosa: que cada dato se almacene una única vez, para eliminar tres anomalías: de actualización (cambiar un correo en diez sitios), de inserción (no poder añadir un producto sin un pedido) y de eliminación (borrar el último pedido borra al cliente). Observa este esquema deliberadamente chapucero.

SQL
CREATE TABLE bad_orders (
  id INTEGER PRIMARY KEY,
  customer_name TEXT,
  customer_email TEXT,
  product_names TEXT,
  total REAL
);

INSERT INTO bad_orders
  (customer_name, customer_email,
   product_names, total)
VALUES
  ('Alice', 'alice@mail.com',
   'keyboard,mouse', 74.0),
  ('Alice', 'alice@mail.co',
   'monitor', 199.0);
Una lista en una columna, un correo duplicado con una errata: está todo ahí.

Las tres primeras formas normales, en la práctica. 1NF: valores atómicos - product_names = 'keyboard,mouse' la viola, lo que vuelve impracticable cualquier WHERE o JOIN sobre los productos. 2NF (clave compuesta): cada columna depende de la clave completa - almacenar el precio de catálogo de un producto en una línea de pedido identificada por (pedido, producto) la viola, porque el precio depende solo del producto. 3NF: ninguna columna que no sea clave depende de otra columna que no sea clave - customer_email, que depende de customer_name, la viola; de ahí el duplicado alice@mail.co. Aquí está el esquema corregido.

SQL
CREATE TABLE customers (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  email TEXT NOT NULL UNIQUE
);

CREATE TABLE products (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  price REAL NOT NULL
);

CREATE TABLE purchases (
  id INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL
    REFERENCES customers(id)
);

CREATE TABLE purchase_items (
  purchase_id INTEGER NOT NULL
    REFERENCES purchases(id),
  product_id INTEGER NOT NULL
    REFERENCES products(id),
  quantity INTEGER NOT NULL,
  PRIMARY KEY (purchase_id, product_id)
);
Cada dato vive en un solo lugar; las claves foráneas lo enlazan todo.
SQL
PRAGMA foreign_keys = ON;

INSERT INTO customers (name, email)
VALUES ('Alice', 'alice@mail.com');

INSERT INTO products (name, price)
VALUES ('keyboard', 49.0), ('mouse', 25.0);

INSERT INTO purchases (customer_id) VALUES (1);

INSERT INTO purchase_items
  (purchase_id, product_id, quantity)
VALUES (1, 1, 1), (1, 2, 1);

SELECT c.name, p.name AS product,
       i.quantity, p.price
FROM purchase_items AS i
JOIN purchases AS pu ON pu.id = i.purchase_id
JOIN customers AS c ON c.id = pu.customer_id
JOIN products AS p ON p.id = i.product_id;
El detalle completo se reconstruye mediante joins.

Entonces, ¿cuándo deberías desnormalizar? Cuando las lecturas se convierten en el cuello de botella medido: uniones de cinco tablas en una pantalla crítica, una agregación recalculada en cada renderizado. Los patrones honestos: una columna de caché (purchases.total congelada en el momento del pedido - y con razón: el precio pagado es un dato histórico, distinto del precio de catálogo), un contador mantenido por un trigger, o una tabla de reportes reconstruida por lotes. El precio a pagar: cada copia puede desviarse, y ahora es tu código, no el motor, el que garantiza la consistencia.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿Qué forma normal viola la columna product_names que contiene 'keyboard,mouse'?
    • 1NF
    • 2NF
    • 3NF
  2. ¿Qué síntoma indica una violación de la 3NF?
    • Una clave primaria formada por dos columnas
    • Una columna que no es clave y depende de otra columna que no es clave
    • Una tabla sin índice secundario
  3. ¿Cuándo se justifica la desnormalización?
    • Desde la etapa de diseño, para ahorrar uniones futuras
    • Después de medir, en las lecturas críticas, aceptando el riesgo de inconsistencia
    • Nunca: un esquema debe permanecer en 3NF pase lo que pase