Relaciones en MySQL

Las bases de datos relacionales deben su nombre a que las tablas se conectan entre sí mediante relaciones. Aprendé cómo funcionan esas conexiones, cómo se protegen con foreign keys y cómo diseñar una base de datos bien estructurada.

¿Qué es una relación?

Una relación es una conexión entre dos tablas que comparten datos en común. En nuestra base de datos tutorial_db, por ejemplo, cada orden pertenece a un cliente. Esa conexión se establece guardando el id del cliente dentro de la tabla ordenes:

-- tabla clientes         -- tabla ordenes
-- id | nombre            -- id | id_cliente | total
-- 1  | Ana García        -- 1  | 3          | 450.00
-- 2  | Carlos López      -- 2  | 1          | 1200.00
-- 3  | Beatriz Torres    -- 3  | 1          | 89.00

La columna id_cliente en ordenes se llama clave foránea o foreign key: es una referencia al id de la tabla clientes. Ese id al que apunta se llama clave primaria o primary key.

Primary Key vs Foreign Key

La Primary Key identifica de forma única cada fila dentro de su tabla — ningún dos registros pueden tener el mismo id. La Foreign Key es una columna en otra tabla que guarda ese id para establecer la relación. Una tabla puede tener muchas foreign keys pero solo una primary key.

Probá el Ejemplo 1: Ver las relaciones de tutorial_db

Tipos de relaciones

Las relaciones entre tablas se clasifican según cuántos registros de cada lado pueden participar en la relación.

Uno a Muchos (1:N) — la más común

Un registro de la tabla A puede relacionarse con muchos registros de la tabla B, pero cada registro de B pertenece a uno solo de A.

-- Un cliente puede tener MUCHAS órdenes
-- Pero cada orden pertenece a UN SOLO cliente

-- clientes (1) ←→ (N) ordenes
-- clientes (1) ←→ (N) ... (relación 1 a muchos)

En nuestra base de datos todas las relaciones son 1:N:

  • Un cliente tiene muchas órdenes
  • Una categoría tiene muchos productos
  • Una orden tiene muchos detalles
  • Un producto aparece en muchos detalles

Muchos a Muchos (N:M)

Un registro de A puede relacionarse con muchos de B, y viceversa. Este tipo de relación no puede representarse directamente con una foreign key — necesita una tabla intermedia que registra cada par de relaciones.

En nuestra base, la relación entre ordenes y productos es N:M: una orden puede tener muchos productos, y un producto puede aparecer en muchas órdenes. La tabla detalle_ordenes es la tabla intermedia:

-- ordenes (N) ←→ detalle_ordenes ←→ (M) productos
--
-- detalle_ordenes tiene:
--   id_orden    → foreign key a ordenes
--   id_producto → foreign key a productos
--   cantidad
--   precio_unitario

Uno a Uno (1:1)

Cada registro de A tiene exactamente un registro en B y viceversa. Se usa para separar datos de una misma entidad cuando hay demasiadas columnas, o cuando ciertos datos son opcionales o sensibles.

-- Ejemplo: tabla usuarios y tabla perfiles
-- Cada usuario tiene exactamente un perfil
-- usuarios (1) ←→ (1) perfiles
¿Cómo identificar el tipo de relación?

Hacete estas preguntas: "¿Un cliente puede tener muchas órdenes?" → Sí. "¿Una orden puede pertenecer a muchos clientes?" → No. Entonces es 1:N con la foreign key en el lado "muchos" (en ordenes).

Integridad referencial

La integridad referencial garantiza que las relaciones entre tablas sean siempre consistentes. En términos prácticos, significa dos cosas:

  • No podés insertar una orden con un id_cliente = 999 si ese cliente no existe
  • No podés eliminar un cliente que todavía tiene órdenes asociadas

MySQL protege estas reglas automáticamente cuando definís una FOREIGN KEY con la opción adecuada.

-- Al crear la tabla ordenes, definimos la foreign key:
CREATE TABLE ordenes (
    id         INT AUTO_INCREMENT PRIMARY KEY,
    id_cliente INT NOT NULL,
    fecha      DATE,
    total      DECIMAL(10,2),
    FOREIGN KEY (id_cliente)
        REFERENCES clientes(id)
        ON DELETE RESTRICT    -- no permite borrar el cliente si tiene órdenes
        ON UPDATE CASCADE     -- si cambia el id del cliente, se actualiza en ordenes
);
ON DELETE y ON UPDATE — las opciones disponibles

RESTRICT — rechaza la operación si hay registros relacionados (la opción más segura).
CASCADE — propaga el cambio: si borrás el cliente, borra todas sus órdenes también.
SET NULL — pone NULL en la foreign key de los registros relacionados.
NO ACTION — igual que RESTRICT en MySQL.

Probá el Ejemplo 2: Ver las foreign keys de tutorial_db

Ver las relaciones de una base de datos

Podés consultar las foreign keys existentes de dos formas. La primera es con SHOW CREATE TABLE, que muestra el SQL completo con el que fue creada la tabla, incluyendo las foreign keys:

SHOW CREATE TABLE ordenes;

La segunda forma, más detallada, es consultar el esquema de información de MySQL que guarda metadata sobre todas las tablas y sus relaciones:

SELECT
    TABLE_NAME        AS tabla,
    COLUMN_NAME       AS columna_fk,
    REFERENCED_TABLE_NAME  AS tabla_referenciada,
    REFERENCED_COLUMN_NAME AS columna_referenciada,
    CONSTRAINT_NAME   AS nombre_constraint
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE()
  AND REFERENCED_TABLE_NAME IS NOT NULL
ORDER BY TABLE_NAME;
information_schema

information_schema es una base de datos especial que MySQL mantiene automáticamente. Contiene información sobre todas tus tablas, columnas, índices, foreign keys y más. Es de solo lectura — no podés modificarla, solo consultarla.

Probá el Ejemplo 3: Consultar foreign keys del sistema

Normalización — organizar bien los datos

La normalización es el proceso de organizar las tablas para eliminar redundancia y evitar inconsistencias. No es una regla rígida, sino una guía para tomar buenas decisiones de diseño.

El problema que resuelve

Imaginá esta tabla sin normalizar:

-- MAL DISEÑO: todo en una sola tabla
-- id | cliente_nombre | cliente_email    | producto    | categoria    | precio
-- 1  | Ana García     | ana@mail.com     | Laptop      | Electrónica  | 1200
-- 2  | Ana García     | ana@mail.com     | Mouse       | Electrónica  | 25
-- 3  | Carlos López   | carlos@mail.com  | Laptop      | Electrónica  | 1200

Los problemas son evidentes:

  • El nombre y email de Ana se repiten en cada compra — si cambia el email, hay que actualizarlo en todas sus filas
  • "Electrónica" se repite en cada producto de esa categoría — si querés renombrarla, tenés que tocar miles de filas
  • El precio de la Laptop aparece en cada venta — si cambia, ¿cuál es el precio real?

El diseño normalizado

Cada entidad vive en su propia tabla. Los datos se relacionan mediante foreign keys en lugar de repetirse:

-- clientes: cada cliente existe una sola vez
-- id | nombre       | email
-- 1  | Ana García   | ana@mail.com
-- 2  | Carlos López | carlos@mail.com

-- categorias: cada categoría existe una sola vez
-- id | nombre
-- 1  | Electrónica

-- productos: cada producto existe una sola vez
-- id | nombre | precio | id_categoria
-- 1  | Laptop | 1200   | 1
-- 2  | Mouse  | 25     | 1

-- ordenes + detalle_ordenes: registran qué compró quién

Ahora si Ana cambia su email, se actualiza en un solo lugar. Si "Electrónica" se renombra a "Tecnología", se cambia en una sola fila.

Regla práctica

Si te encontrás copiando el mismo dato en múltiples filas, probablemente necesitás una tabla separada y una foreign key. El principio es: cada dato debe existir en un único lugar.

Ejemplo Completo — analizar la estructura de tutorial_db

Con todo lo aprendido, podés hacer una query que muestre el mapa completo de relaciones de la base de datos: cuántas filas tiene cada tabla y cómo se conectan entre sí.

-- Contar registros en cada tabla para entender la escala
SELECT 'clientes'        AS tabla, COUNT(*) AS registros FROM clientes
UNION ALL
SELECT 'categorias',              COUNT(*)               FROM categorias
UNION ALL
SELECT 'productos',               COUNT(*)               FROM productos
UNION ALL
SELECT 'ordenes',                 COUNT(*)               FROM ordenes
UNION ALL
SELECT 'detalle_ordenes',         COUNT(*)               FROM detalle_ordenes
ORDER BY registros DESC;
Probá el Ejemplo 4: Mapa de la base de datos

Buenas Prácticas

  • Siempre definí PRIMARY KEY — cada tabla debería tener un identificador único, generalmente id INT AUTO_INCREMENT
  • Usá FOREIGN KEY con RESTRICT por defecto — es la opción más segura; evita borrados accidentales en cascada
  • Nombrá las constraints descriptivamente — fk_ordenes_clientes es mejor que dejar que MySQL le ponga un nombre automático
  • Un dato, un lugar — si repetís el mismo dato en varias filas, algo en el diseño está mal
  • Indexá las foreign keys — MySQL lo hace automáticamente en InnoDB, pero si usás otro motor hay que hacerlo a mano
  • Dibujá el diagrama antes de crear las tablas — un papel con las entidades y sus relaciones ahorra muchos problemas después