Tablas puente en relaciones N:N
Qué es una relación N-a-N
Una relación N-a-N significa que:
- Un registro de la tabla A puede relacionarse con muchos registros de la tabla B.
- Y un registro de la tabla B puede relacionarse con muchos registros de la tabla A.
Ejemplos comunes:
- Un alumno puede matricularse en muchos cursos, y un curso tener muchos alumnos.
- Un pedido puede incluir muchos productos, y un producto estar en muchos pedidos.
- Un usuario puede tener varios roles, y un rol pertenecer a varios usuarios.
Para representar esto no podemos usar una FK directa como en 1-a-N.
Necesitamos una tabla puente (o tabla intermedia).
Tabla puente: estructura base
Supón esta relación:
ALUMNO (N) ⟷ (N) CURSO
La forma correcta de representarla es:
ALUMNO
|
| 1-a-N
v
ALUMNO_CURSO (tabla puente)
^
| N-a-1
|
CURSO
Ejemplo DBML. Puedes probarlo en drawDB.app
Table alumno {
id_alumno int [pk, increment]
nombre varchar(100) [not null]
}
Table curso {
id_curso int [pk, increment]
titulo varchar(100) [not null]
}
Table alumno_curso {
id_alumno int [not null]
id_curso int [not null]
fecha_matricula date [not null, default: `CURRENT_DATE`]
indexes {
(id_alumno, id_curso) [pk]
}
}
Ref: alumno_curso.id_alumno > alumno.id_alumno
Ref: alumno_curso.id_curso > curso.id_curso
La tabla alumno_curso:
- Tiene dos claves foráneas.
- La clave primaria compuesta (
id_alumno,id_curso) garantiza que un alumno no pueda matricularse dos veces en el mismo curso. fecha_matriculaes un atributo propio de la relación.
Atributos en la relación
En 1-a-N, los atributos suelen ir en la tabla hija. En N-a-N, los atributos pertenecen a la relación misma y por eso van en la tabla puente.
Ejemplos:
- Fecha de matrícula en curso.
- Cantidad de productos en un pedido.
- Rol asignado a un usuario.
- Precio aplicado en el momento de la venta.
Importante: no pongas esos datos en las tablas principales, porque pueden repetirse muchas veces con valores distintos.
Ejemplo con productos y pedidos:
Table producto {
id_producto int [pk, increment]
nombre varchar(100)
}
Table pedido {
id_pedido int [pk, increment]
fecha date [not null]
}
Table pedido_producto {
id_pedido int [not null]
id_producto int [not null]
cantidad int [not null] // CHECK (cantidad > 0) no se representa en DBML, pero lo anoto aquí
precio_unitario numeric(10,2) [not null]
indexes {
(id_pedido, id_producto) [pk]
}
}
Ref: pedido_producto.id_pedido > pedido.id_pedido
Ref: pedido_producto.id_producto > producto.id_producto
cantidad y precio_unitario son propios de la relación pedido-producto, no del producto ni del pedido por separado.
Claves compuestas: por qué son importantes
La clave primaria compuesta (FK A + FK B) evita duplicados sin necesidad de un ID artificial:
- Garantiza que un alumno no se inscriba dos veces en el mismo curso.
- Que un producto no aparezca dos veces en el mismo pedido.
- Que un rol no se asigne dos veces al mismo usuario.
También puedes usar una PK artificial (ej. id SERIAL) si:
- La tabla puente tiene muchos atributos adicionales,
- O si vas a referenciar la relación desde otras tablas.
Pero en muchos casos, la PK compuesta es más natural y eficiente.
Ejemplo práctico — usuarios y roles
Supongamos que tenemos autenticación:
- Tabla
usuario - Tabla
rol - Un usuario puede tener varios roles, y un rol puede pertenecer a varios usuarios.
Table usuario {
id_usuario int [pk, increment]
nombre varchar(100) [not null]
}
Table rol {
id_rol int [pk, increment]
nombre varchar(50) [unique, not null]
}
Table usuario_rol {
id_usuario int [not null]
id_rol int [not null]
asignado_en timestamp [default: `CURRENT_TIMESTAMP`]
indexes {
(id_usuario, id_rol) [pk]
}
}
Ref: usuario_rol.id_usuario > usuario.id_usuario
Ref: usuario_rol.id_rol > rol.id_rol
Así:
- Cada usuario puede tener varios roles, pero no se duplica ninguno.
- Podemos saber cuándo se asignó cada rol.
- La relación queda clara y sin datos repetidos.
Tablas puente con atributos propios y PK artificial
Si necesitas identificar la relación individualmente (por ejemplo, para auditarla o referenciarla desde otra tabla), puedes usar un id artificial además de las FKs.
Table inscripcion {
id int [pk, increment]
id_alumno int [not null]
id_curso int [not null]
fecha_inscripcion date [default: `CURRENT_DATE`]
nota numeric(4,2)
indexes {
(id_alumno, id_curso) [unique]
}
}
Ref: inscripcion.id_alumno > alumno.id_alumno
Ref: inscripcion.id_curso > curso.id_curso
Aquí:
idsirve como identificador único de inscripción.- La restricción UNIQUE mantiene la integridad N-a-N.
- Se pueden añadir más atributos (nota, estado, etc.) fácilmente.
Buenas prácticas para modelar N-a-N
- Usa tabla puente siempre: no intentes “guardar arrays” en una sola columna.
- Define PK compuesta si solo almacenas las FKs y algún atributo simple.
- Usa PK artificial + UNIQUE si la relación tiene atributos complejos.
- Aplica
CHECKa los atributos de relación para validar reglas (ej.cantidad > 0). - Documenta la relación: es parte clave del modelo conceptual.
- Define ON DELETE/UPDATE adecuadamente para mantener integridad (por ejemplo,
CASCADEen relaciones débiles).
Errores comunes
- Guardar IDs separados por comas en un campo
VARCHAR(anti-patrón clásico). - No usar PK compuesta ni UNIQUE → duplicados silenciosos.
- No usar tabla puente → modelo inconsistente.
- Meter atributos en tablas equivocadas (producto/pedido en lugar de pedido_producto).
- No documentar reglas de unicidad.
Con este módulo, el estudiante:
- Comprende cuándo y cómo usar tablas puente,
- Entiende la diferencia entre atributos de entidad y de relación,
- Y evita anti-patrones muy comunes en principiantes.