Crear tablas relacionales con foreign keys en MySQL implica decidir entre relaciones duras (impuestas por la base de datos) y relaciones suaves (gestionadas desde la aplicación). Aquí verás cómo construir la tabla que cierra el círculo de un sistema de facturación, qué pasa cuando usas ON DELETE CASCADE y por qué a veces conviene soltar las restricciones.
Por qué nombrar bien una tabla pivote en SQL
Antes de escribir cualquier CREATE TABLE, vale la pena detenerse en el nombre. La tabla que une bills y products podría llamarse bill_items, pero si nadie en el proyecto usa la palabra items, alguien nuevo va a buscar esa entidad y no la va a encontrar. Por eso conviene renombrarla a bill_products: refleja exactamente lo que guarda y se mantiene cercano a la realidad del negocio [03:15].
Esta práctica de usar tus propias convenciones es una habilidad clave del modelado de datos. No se trata de seguir una regla universal, sino de que tu esquema cuente la historia correcta del producto.
¿Qué es una tabla pivote en bases de datos relacionales? Es la tabla que conecta dos entidades con relación muchos a muchos. En este caso, bill_products registra qué productos aparecen en qué facturas y en qué cantidad.
Cómo se escribe un CREATE TABLE con foreign keys en MySQL
La estructura de bill_products arranca con una llave primaria autoincremental y dos columnas que apuntan a las tablas relacionadas. La cantidad nunca debe ser cero, así que se define con un valor por defecto de uno.
sql
CREATE TABLE IF NOT EXISTS bill_products (
bill_product_id INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,
bill_id INTEGER UNSIGNED NOT NULL,
product_id INTEGER UNSIGNED NOT NULL,
quantity INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (bill_id) REFERENCES bills(bill_id) ON UPDATE CASCADE ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(product_id) ON UPDATE CASCADE ON DELETE CASCADE
);
Nota que en DBML las definiciones van entre llaves, mientras que en SQL puro se usan paréntesis. Es un detalle pequeño pero importante cuando traduces tu diagrama al código real [04:20].
Qué opciones existen para ON UPDATE y ON DELETE
MySQL ofrece varias acciones referenciales, aunque no todas funcionan en cualquier escenario. Su disponibilidad depende del motor de almacenamiento, especialmente InnoDB, que es el más usado.
CASCADE: propaga el cambio o el borrado a las tablas hijas.
SET NULL: pone la columna en NULL, pero falla si la columna está marcada como NOT NULL.
SET DEFAULT: prácticamente no funciona en InnoDB, así que es casi inexistente en la práctica.
NO ACTION / RESTRICT: bloquea la operación si rompe la integridad.
Qué pasa cuando ejecutas un DELETE con ON DELETE CASCADE
Una vez creadas las cuatro tablas (clients, products, bills y bill_products), el orden de inserción importa: primero clientes y productos, luego facturas, y al final los renglones de la factura. Si rompes ese orden con relaciones duras, MySQL te detiene.
Lo interesante viene al borrar. Un simple DELETE FROM clients sin WHERE borra todos los clientes, y como bills apunta a clients con ON DELETE CASCADE, también desaparecen las facturas. Y como bill_products apunta a bills con la misma regla, también se va su contenido. Una sola sentencia vacía tres tablas [09:30].
Este comportamiento es increíblemente potente y, por lo mismo, increíblemente peligroso si se configura sin pensarlo bien.
¿Qué hace ON DELETE CASCADE en MySQL? Cuando borras un registro padre, MySQL borra automáticamente todos los registros hijos que lo referencian. Si esa cadena tiene varios niveles, el efecto se propaga hasta el final.
Cuándo usar relaciones duras y cuándo relaciones suaves
Una relación dura vive en la base de datos: las foreign keys obligan a que los datos sean consistentes. Una relación suave vive en la capa de modelo de tu aplicación: tú validas, desde el código, que un bill_id exista antes de insertarlo.
Hay quien sostiene que una base de datos relacional sin foreign keys deja de ser relacional. Es una postura discutible. Lo relacional incluye cómo planeas almacenar los datos, cómo los normalizas y cómo los explotas, no solo dónde imponen las reglas.
Para pasar de relaciones duras a suaves, basta con eliminar las cláusulas FOREIGN KEY del CREATE TABLE, hacer DROP TABLE en orden inverso de dependencia y volver a crear las tablas:
- Borra primero la tabla hija (
bill_products).
- Borra después la tabla padre (
bills).
- Recrea ambas sin las restricciones.
Una vez sin restricciones, puedes insertar un bill_product con product_id = 1000 y bill_id = 10005 aunque esos IDs no existan en ningún lado. La base no protesta. Si haces un JOIN después, te va a unir contra la nada [16:45].
Cuáles son los pros y contras de cada enfoque
La decisión depende del tipo de proyecto, del equipo y del ritmo de cambio del esquema.
- Relaciones duras: máxima integridad, ideal para equipos grandes donde la lógica de negocio no siempre está bien estructurada. Pero modificar tablas se vuelve un dolor de cabeza.
- Relaciones suaves: mantenibilidad y velocidad de desarrollo, ideales cuando el producto crece rápido. La contra es que la integridad depende totalmente de tu aplicación.
No hay una respuesta única. Con los años vas a notar qué encaja mejor con tu estilo y con cada proyecto. ¿Tú con cuál te quedas, llaves duras o llaves suaves? Cuéntalo en los comentarios.