Mira la lección en vídeo
Accede al vídeo completo de esta lección y a más contenido exclusivo con el Plan Plus.
Desbloquear Plan PlusRelación One-to-Many con relationship()
Las relaciones One-to-Many representan uno de los patrones más comunes en bases de datos relacionales. En este tipo de relación, un registro de una tabla puede estar asociado con múltiples registros de otra tabla. Por ejemplo, un usuario puede tener múltiples publicaciones, o una categoría puede contener múltiples productos.
SQLAlchemy nos proporciona la función relationship()
que simplifica enormemente el trabajo con estas asociaciones. En lugar de realizar joins manuales cada vez que necesitamos datos relacionados, podemos definir la relación una vez en nuestros modelos y SQLAlchemy se encarga del resto.
Definición básica de la relación
Para establecer una relación One-to-Many, necesitamos definir la clave foránea en el modelo "Many" y usar relationship()
en el modelo "One". Veamos un ejemplo práctico con usuarios y publicaciones:
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from database import Base
class Usuario(Base):
__tablename__ = "usuarios"
id = Column(Integer, primary_key=True, index=True)
nombre = Column(String, index=True)
email = Column(String, unique=True, index=True)
# Relación One-to-Many: un usuario tiene muchas publicaciones
publicaciones = relationship("Publicacion", back_populates="autor")
class Publicacion(Base):
__tablename__ = "publicaciones"
id = Column(Integer, primary_key=True, index=True)
titulo = Column(String, index=True)
contenido = Column(String)
usuario_id = Column(Integer, ForeignKey("usuarios.id"))
# Relación Many-to-One: muchas publicaciones pertenecen a un usuario
autor = relationship("Usuario", back_populates="publicaciones")
En este ejemplo, el atributo publicaciones
en el modelo Usuario
nos permite acceder a todas las publicaciones de ese usuario sin escribir consultas SQL adicionales. El parámetro back_populates
establece una relación bidireccional, permitiendo navegar en ambas direcciones.
Uso práctico de las relaciones
Una vez definidas las relaciones, podemos trabajar con los datos de forma muy intuitiva. SQLAlchemy se encarga automáticamente de las consultas necesarias:
from sqlalchemy.orm import Session
def crear_usuario_con_publicaciones(db: Session):
# Crear un nuevo usuario
nuevo_usuario = Usuario(
nombre="Ana García",
email="ana@ejemplo.com"
)
db.add(nuevo_usuario)
db.commit()
db.refresh(nuevo_usuario)
# Crear publicaciones para este usuario
publicacion1 = Publicacion(
titulo="Mi primera publicación",
contenido="Contenido de la primera publicación",
usuario_id=nuevo_usuario.id
)
publicacion2 = Publicacion(
titulo="Segunda publicación",
contenido="Más contenido interesante",
usuario_id=nuevo_usuario.id
)
db.add_all([publicacion1, publicacion2])
db.commit()
return nuevo_usuario
Para acceder a los datos relacionados, simplemente utilizamos los atributos definidos en la relación:
def obtener_usuario_con_publicaciones(db: Session, usuario_id: int):
usuario = db.query(Usuario).filter(Usuario.id == usuario_id).first()
if usuario:
print(f"Usuario: {usuario.nombre}")
print(f"Email: {usuario.email}")
print("Publicaciones:")
# Acceso directo a las publicaciones relacionadas
for publicacion in usuario.publicaciones:
print(f"- {publicacion.titulo}")
return usuario
Configuración avanzada de relationship()
La función relationship()
acepta varios parámetros que nos permiten personalizar el comportamiento de la relación:
class Usuario(Base):
__tablename__ = "usuarios"
id = Column(Integer, primary_key=True, index=True)
nombre = Column(String, index=True)
# Relación con configuración adicional
publicaciones = relationship(
"Publicacion",
back_populates="autor",
lazy="select", # Estrategia de carga
order_by="Publicacion.titulo" # Ordenar por título
)
El parámetro lazy
controla cuándo se cargan los datos relacionados. El valor "select"
(por defecto) carga los datos cuando se accede por primera vez al atributo. El parámetro order_by
permite especificar un orden predeterminado para los registros relacionados.
Integración con FastAPI
En nuestras rutas de FastAPI, podemos aprovechar estas relaciones para crear endpoints más eficientes:
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from database import get_db
app = FastAPI()
@app.get("/usuarios/{usuario_id}/publicaciones")
def obtener_publicaciones_usuario(usuario_id: int, db: Session = Depends(get_db)):
usuario = db.query(Usuario).filter(Usuario.id == usuario_id).first()
if not usuario:
raise HTTPException(status_code=404, detail="Usuario no encontrado")
# Retornamos las publicaciones usando la relación definida
return {
"usuario": usuario.nombre,
"publicaciones": [
{
"id": pub.id,
"titulo": pub.titulo,
"contenido": pub.contenido
}
for pub in usuario.publicaciones
]
}
Esta aproximación es mucho más limpia y mantenible que realizar joins manuales en cada consulta. SQLAlchemy optimiza automáticamente las consultas y nos proporciona una interfaz orientada a objetos para trabajar con los datos relacionados.
Las relaciones One-to-Many con relationship()
forman la base para construir modelos de datos robustos en aplicaciones FastAPI, permitiendo representar de forma natural las asociaciones entre entidades del mundo real.
Relación Many-to-One y foreign keys
Guarda tu progreso
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
La relación Many-to-One es el complemento natural de las relaciones One-to-Many que acabamos de ver. Mientras que en One-to-Many nos enfocamos en cómo un registro puede tener múltiples registros relacionados, en Many-to-One nos centramos en cómo múltiples registros apuntan hacia un único registro padre.
Esta relación se implementa mediante claves foráneas (foreign keys), que son columnas que referencian la clave primaria de otra tabla. Las foreign keys garantizan la integridad referencial de nuestra base de datos, asegurando que las relaciones entre tablas sean válidas y consistentes.
Definición de foreign keys en SQLAlchemy
Para crear una foreign key en SQLAlchemy, utilizamos la función ForeignKey()
junto con Column()
. La sintaxis básica requiere especificar la tabla y columna de destino:
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from database import Base
class Categoria(Base):
__tablename__ = "categorias"
id = Column(Integer, primary_key=True, index=True)
nombre = Column(String, unique=True, index=True)
descripcion = Column(String)
class Producto(Base):
__tablename__ = "productos"
id = Column(Integer, primary_key=True, index=True)
nombre = Column(String, index=True)
precio = Column(Integer) # Precio en céntimos
# Foreign key que referencia la tabla categorias
categoria_id = Column(Integer, ForeignKey("categorias.id"))
# Relación Many-to-One: muchos productos pertenecen a una categoría
categoria = relationship("Categoria")
En este ejemplo, categoria_id
es la foreign key que conecta cada producto con su categoría correspondiente. La relación categoria
nos permite acceder directamente al objeto Categoria
asociado sin realizar consultas adicionales.
Trabajando con relaciones Many-to-One
Las relaciones Many-to-One son especialmente útiles cuando necesitamos agrupar registros bajo una entidad común. Veamos cómo crear y consultar estos datos:
from sqlalchemy.orm import Session
def crear_productos_por_categoria(db: Session):
# Crear categorías
categoria_electronica = Categoria(
nombre="Electrónica",
descripcion="Dispositivos y componentes electrónicos"
)
categoria_hogar = Categoria(
nombre="Hogar",
descripcion="Artículos para el hogar"
)
db.add_all([categoria_electronica, categoria_hogar])
db.commit()
db.refresh(categoria_electronica)
db.refresh(categoria_hogar)
# Crear productos asociados a las categorías
productos = [
Producto(
nombre="Smartphone",
precio=59999, # 599.99 euros en céntimos
categoria_id=categoria_electronica.id
),
Producto(
nombre="Tablet",
precio=29999,
categoria_id=categoria_electronica.id
),
Producto(
nombre="Aspiradora",
precio=15999,
categoria_id=categoria_hogar.id
)
]
db.add_all(productos)
db.commit()
return productos
Para consultar datos relacionados, podemos acceder directamente al objeto padre a través de la relación:
def mostrar_productos_con_categoria(db: Session):
productos = db.query(Producto).all()
for producto in productos:
print(f"Producto: {producto.nombre}")
print(f"Precio: {producto.precio / 100:.2f}€")
# Acceso directo a la categoría relacionada
print(f"Categoría: {producto.categoria.nombre}")
print(f"Descripción: {producto.categoria.descripcion}")
print("---")
Restricciones y validación de foreign keys
Las foreign keys proporcionan validación automática a nivel de base de datos. Si intentamos insertar un producto con un categoria_id
que no existe, la base de datos rechazará la operación:
def ejemplo_foreign_key_invalida(db: Session):
try:
# Intentar crear un producto con categoria_id inexistente
producto_invalido = Producto(
nombre="Producto sin categoría",
precio=1000,
categoria_id=999 # ID que no existe
)
db.add(producto_invalido)
db.commit()
except Exception as e:
db.rollback()
print(f"Error: {e}")
# La base de datos rechaza la inserción
Consultas eficientes con foreign keys
Podemos optimizar nuestras consultas utilizando joins explícitos cuando necesitamos datos de ambas tablas:
from sqlalchemy.orm import joinedload
def obtener_productos_con_categoria_optimizado(db: Session):
# Carga los productos y sus categorías en una sola consulta
productos = db.query(Producto).options(
joinedload(Producto.categoria)
).all()
# Ahora podemos acceder a la categoría sin consultas adicionales
for producto in productos:
print(f"{producto.nombre} - {producto.categoria.nombre}")
return productos
Implementación en endpoints de FastAPI
En nuestras rutas de FastAPI, las relaciones Many-to-One nos permiten crear endpoints más informativos:
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from database import get_db
app = FastAPI()
@app.get("/productos/{producto_id}")
def obtener_producto(producto_id: int, db: Session = Depends(get_db)):
producto = db.query(Producto).filter(Producto.id == producto_id).first()
if not producto:
raise HTTPException(status_code=404, detail="Producto no encontrado")
return {
"id": producto.id,
"nombre": producto.nombre,
"precio": producto.precio / 100, # Convertir céntimos a euros
"categoria": {
"id": producto.categoria.id,
"nombre": producto.categoria.nombre,
"descripcion": producto.categoria.descripcion
}
}
@app.get("/productos/categoria/{categoria_id}")
def obtener_productos_por_categoria(categoria_id: int, db: Session = Depends(get_db)):
# Verificar que la categoría existe
categoria = db.query(Categoria).filter(Categoria.id == categoria_id).first()
if not categoria:
raise HTTPException(status_code=404, detail="Categoría no encontrada")
# Obtener productos de esa categoría
productos = db.query(Producto).filter(Producto.categoria_id == categoria_id).all()
return {
"categoria": categoria.nombre,
"productos": [
{
"id": p.id,
"nombre": p.nombre,
"precio": p.precio / 100
}
for p in productos
]
}
Las foreign keys son fundamentales para mantener la consistencia de los datos y establecer relaciones claras entre entidades. Al combinarlas con relationship()
, obtenemos una forma elegante y eficiente de trabajar con datos relacionados en nuestras aplicaciones FastAPI.
Aprendizajes de esta lección de FastAPI
- Comprender el concepto de relaciones One-to-Many y Many-to-One en bases de datos relacionales.
- Aprender a definir claves foráneas y relaciones bidireccionales con SQLAlchemy usando ForeignKey y relationship().
- Saber cómo manipular y consultar datos relacionados de forma eficiente mediante atributos de relación.
- Configurar parámetros avanzados de relationship() para optimizar la carga y ordenación de datos.
- Integrar estas relaciones en endpoints de FastAPI para construir APIs limpias y mantenibles.
Completa este curso de FastAPI y certifícate
Únete a nuestra plataforma de cursos de programación y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs