Saltar a contenido

Relaciones

class Post(Table):
    __tablename__ = 'post'

    id: col[int] = column(primary_key=True, autoincrement=True)
    titulo: col[str]
    autor_id: col[int]                       # la columna FK, declarada a mano

    autor: manytoone[Autor] = relation(
        fields=['autor_id'],                 # columnas de ESTA tabla
        references=['id'],                   # columnas de la tabla destino
        backref='posts',                     # cómo se llama el lado inverso
        onDelete='cascade',                  # 'cascade' | 'set null' | 'restrict'
        onUpdate='cascade',
    )

Los tres primeros argumentos (fields, references, backref) son obligatorios.

relation() se declara solo en la tabla que posee las columnas FK

Es la regla que más se incumple. El otro lado es implícito:

class Autor(Table):
    posts: onetomany[Post]                    # ✅ el backref de la relación de Post
    # posts: onetomany[Post] = relation(...)  # ❌ NO: la FK no vive aquí

Las tres cardinalidades

class Autor(Table):
    __tablename__ = 'autor'
    id: col[int] = column(primary_key=True, autoincrement=True)
    nombre: col[str]

    posts: onetomany[Post]          # lado implícito


class Post(Table):
    __tablename__ = 'post'
    id: col[int] = column(primary_key=True, autoincrement=True)
    autor_id: col[int]

    autor: manytoone[Autor] = relation(
        fields=['autor_id'], references=['id'], backref='posts'
    )
class Usuario(Table):
    __tablename__ = 'usuario'
    id: col[int] = column(primary_key=True, autoincrement=True)

    perfil: onetoone[Perfil]


class Perfil(Table):
    __tablename__ = 'perfil'
    id: col[int] = column(primary_key=True, autoincrement=True)
    usuario_id: col[int] = column(unique=True)   # la unicidad es lo que lo hace 1:1

    usuario: onetoone[Usuario] = relation(
        fields=['usuario_id'], references=['id'], backref='perfil'
    )

La sintaxis v1 no sabe expresar un uno a uno: un hint escalar se interpreta siempre como many-to-one.

No hay una declaración especial: se modela con la tabla intermedia, que es lo que existe de verdad en la base de datos.

class UsuarioRol(Table):
    __tablename__ = 'usuario_rol'
    __constraints__ = unique_constraint('usuario_id', 'rol_id')

    id: col[int] = column(primary_key=True, autoincrement=True)
    usuario_id: col[int]
    rol_id: col[int]

    usuario: manytoone[Usuario] = relation(
        fields=['usuario_id'], references=['id'], backref='roles'
    )
    rol: manytoone[Rol] = relation(
        fields=['rol_id'], references=['id'], backref='usuarios'
    )

Claves compuestas

fields y references son listas porque una FK puede abarcar varias columnas:

linea: manytoone[LineaPedido] = relation(
    fields=['pedido_id', 'linea_num'],
    references=['pedido_id', 'num'],
    backref='envios',
)

Qué pasa con onDelete y onUpdate

Se traducen a las cláusulas ON DELETE / ON UPDATE de la clave foránea, y por tanto las impone la base de datos, no el cliente generado.

Valor Efecto al borrar la fila referenciada
'cascade' (por defecto) Borra también las filas que la referencian
'set null' Pone la FK a NULL (la columna tiene que ser opcional)
'restrict' Impide el borrado

Límites

  • Las relaciones entre schemas distintos no se declaran. Son dos bases de datos lógicas separadas; si lo intentas, el análisis lo dice (E003: «apunta a X, que no está declarado en este schema»).
  • Una constraint no se declara sobre el nombre de una relación, sino sobre las columnas físicas: usuario_id, no usuario.
  • Para cargar relaciones al consultar, ver includes.