Fundamentos de Normalización
7 ejemplos para empezar con la Normalización: 5 básicos y 2 intermedios.
Prerrequisitos
- PostgreSQL 18.4 con una base de datos de prueba para experimentos DDL.
- Familiaridad con
CREATE TABLE,PRIMARY KEYyFOREIGN KEY.
Ejemplos Básicos
1. Anomalía de Actualización en una Tabla Amplia
Los datos duplicados fuerzan actualizaciones de varias filas cuando un hecho cambia.
CREATE TABLE order_lines_bad (
order_id integer,
customer_email text,
customer_city text,
product_sku text,
product_name text,
quantity integer,
PRIMARY KEY (order_id, product_sku)
);
-- El cliente se muda de ciudad: hay que actualizar cada fila histórica
UPDATE order_lines_bad SET customer_city = 'Portland' WHERE customer_email = 'a@example.com';- Almacenar
customer_cityen cada línea crea una anomalía de actualización. - Olvidar una fila deja valores de ciudad contradictorios para el mismo correo electrónico.
- La normalización divide los hechos estables en sus propias tablas.
Relacionado: 3NF y BCNF - formas normales formales
2. Descomponer Cliente de Líneas de Pedido
Mueve los atributos repetidos del cliente a una tabla principal.
CREATE TABLE customers (
customer_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email text NOT NULL UNIQUE,
city text NOT NULL
);
CREATE TABLE orders (
order_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_id bigint NOT NULL REFERENCES customers (customer_id)
);
CREATE TABLE order_items (
order_id bigint NOT NULL REFERENCES orders (order_id),
product_id bigint NOT NULL,
quantity integer NOT NULL CHECK (quantity > 0),
PRIMARY KEY (order_id, product_id)
);- La ciudad del cliente vive en una sola fila: actualiza una vez.
- Los pedidos hacen referencia a los clientes por FK: la anomalía de inserción se evita con una fila principal explícita.
- Los artículos de línea hacen referencia a los pedidos: las reglas de eliminación se controlan con
ON DELETE.
Relacionado: Modelado Entidad-Relación - colocación de FK
3. Anomalía de Inserción Sin Tabla de Producto
No puedes añadir un producto del catálogo hasta que alguien lo pida en un diseño desnormalizado.
CREATE TABLE products (
product_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
sku text NOT NULL UNIQUE,
name text NOT NULL
);
-- El equipo de catálogo puede INSERTAR productos antes de que exista ningún pedido
INSERT INTO products (sku, name) VALUES ('WIDGET-1', 'Widget');- La dimensión de producto normalizada previene anomalías de inserción.
order_itemshace referencia aproductscuando ocurre una venta.UNIQUEenskuimpone la identidad del catálogo.
Relacionado: Desnormalización Intencional - cuándo romper las reglas deliberadamente
4. La Anomalía de Eliminación Conserva el Catálogo
Eliminar el último pedido de un producto no debe eliminar la definición del producto.
CREATE TABLE order_items (
order_id bigint NOT NULL REFERENCES orders (order_id) ON DELETE CASCADE,
product_id bigint NOT NULL REFERENCES products (product_id) ON DELETE RESTRICT,
quantity integer NOT NULL,
PRIMARY KEY (order_id, product_id)
);ON DELETE RESTRICTenproduct_idimpide eliminar productos que todavía se referencian.ON DELETE CASCADEenorder_idelimina los artículos de línea cuando se cancela un pedido.- Separar entidades elimina anomalías de eliminación en datos de referencia compartidos.
5. La Dependencia Funcional Impulsa la Colocación de Columnas
Si sku -> product_name, product_name pertenece con sku, no en cada fila de hecho.
CREATE TABLE products (
product_id bigint PRIMARY KEY,
sku text NOT NULL UNIQUE,
name text NOT NULL
);
-- name está determinado por sku (y product_id), no por order_id- Una dependencia funcional
A -> Bsignifica que B pertenece a la misma tabla que A. - Las tablas de hechos contienen IDs y medidas (
quantity), no duplicados descriptivos. - Documenta las dependencias en comentarios de migración para futuros revisores.
Ejemplos Intermedios
6. Dependencia Transitiva (Hacia 3NF)
La ciudad determinada por el código postal no debe estar en el cliente si el código postal determina la ciudad.
CREATE TABLE zip_codes (
zip text PRIMARY KEY,
city text NOT NULL,
state text NOT NULL
);
CREATE TABLE customers (
customer_id bigint PRIMARY KEY,
email text NOT NULL UNIQUE,
zip text NOT NULL REFERENCES zip_codes (zip)
);zip -> cityes transitiva cuando ambas estaban encustomerscon solocustomer_idcomo clave.- La tabla de referencia
zip_codescontiene los atributos de ubicación una vez. - El coste de la unión es la contrapartida; indexa
zipencustomers.
Relacionado: 3NF y BCNF - dependencias transitivas
7. Detectar Duplicados con una Consulta
Encuentra duplicados contradictorios antes de que lleguen a los informes de producción.
SELECT customer_email, COUNT(DISTINCT customer_city) AS city_variants
FROM order_lines_bad
GROUP BY customer_email
HAVING COUNT(DISTINCT customer_city) > 1;- Los problemas de normalización aparecen como múltiples valores distintos para el mismo determinante.
- Ejecuta después de las importaciones ETL y antes de desnormalizar por rendimiento.
- Cero filas es el objetivo; filas distintas de cero desencadenan trabajo de descomposición.
Relacionado: Mejores Prácticas de Desnormalización - reglas seguras de desnormalización
Versiones de Stack: Esta página fue escrita para PostgreSQL 18.4 (estable 18, mantenimiento 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x y PostGIS 3.5+.