Recientemente comencé a trabajar con SQL Alchemy para un proyecto que involucra áreas y rutas de escalada. Las áreas son jerárquicas porque una sola área puede contener múltiples áreas, que a su vez pueden contener otras áreas. Una ruta está directamente asociada con un área única, pero también está asociada con el área principal de esa área, etc.SQL Alchemy Closure Table Relación Definición
Para implementar esto, elegí usar closure table ala Bill Karwin. En la implementación de la tabla de cierre, se crea una segunda tabla para almacenar la información de ancestro/descendiente. Se crea una fila de autorreferencia cuando se agrega un nodo, así como una fila para cada antecesor en el árbol.
La estructura de la tabla es la siguiente (simplificado):
-- area --
area_id
name
-- area_relationship --
ancestor
descendent
-- route --
route_id
area_id
name
datos de la muestra:
-- area --
1, New River Gorge
2, Kaymoor
3, South Nuttall
4, Meadow River Gorge
-- area_relationship (ancestor, descendent) --
1, 1 (self-referencing)
2, 2 (self-referencing)
1, 2 (Kaymoor is w/i New River Gorge)
3, 3 (self-referencing)
1, 3 (South Nutall is w/i New River Gorge)
4, 4 (self-referencing)
-- route (route_id, area_id, name)
1, 2, Leave it to Jesus
2, 2, Green Piece
3, 4, Fancy Pants
para consultar todas las áreas para una ruta determinada (el árbol), que se puede ejecutar:
SELECT area.area_id, area.name
FROM route
INNER JOIN area_relationship ON route.area_id = area_relationship.descendent
INNER JOIN area ON area.area_id = area_relationship.ancestor
WHERE route.route_id = 1
Del mismo modo, puedo consultar para todas las rutas en un área en particular (incluyendo áreas descendientes) con:
SELECT route.route_id, route.name
FROM area
INNER JOIN area_relationship ON area.area_id = area_relationship.ancestor
INNER JOIN route ON route.area_id = area_relationship.descendent
WHERE area.area_id = 1
En SQL Alchemy He creado una relación y dos mesas para manejar estas relaciones: clase
area_relationship_table = Table('area_relationship', Base.metadata,
Column('ancestor', Integer, ForeignKey('area.area_id')),
Column('descendent', Integer, ForeignKey('area.area_id'))
)
DbArea -
claseclass DbArea(Base):
__tablename__ = 'area'
area_id = Column(Integer, primary_key = True)
name = Column(VARCHAR(50))
created = Column(DATETIME)
area_relationship_table.c.ancestor])
descendents = relationship('DbArea', backref = 'ancestors',
secondary = area_relationship_table,
primaryjoin = area_id == area_relationship_table.c.ancestor,
secondaryjoin = area_id == area_relationship_table.c.descendent)
DbRoute -
class DbRoute(Base):
__tablename__ = 'route'
route_id = Column(Integer, primary_key = True)
area_id = Column(Integer, ForeignKey('area.area_id'))
name = Column(VARCHAR(50))
created = Column(DATETIME)
area = relationship("DbArea")
areas = relationship('DbArea', backref = 'routes',
secondary = area_relationship_table,
primaryjoin = area_id == area_relationship_table.c.ancestor,
secondaryjoin = area_id == area_relationship_table.c.descendent,
foreign_keys=[area_relationship_table.c.ancestor,
area_relationship_table.c.descendent])
Actualmente, soy capaz de determinar las áreas de la ruta individual, usando la relación de áreas en DbRoute. Sin embargo, cuando trato de usar las 'rutas' de respaldo en DbArea, aparece el siguiente error:
sqlalchemy.exc.StatementError: No hay ninguna columna route.area_id configurada en el asignador Mapper | DbArea | area ... (original causa: UnmappedColumnError: Ninguna columna route.area_id está configurada en el asignador Mapper | DbArea | área ...) 'SELECT route.route_id AS route_route_id, route.area_id AS route_area_id, route.name AS route_name, route.created AS route_created \ nFROM route , area_relationship \ nWHERE% s = area_relationship.descendent AND route.area_id = area_relationship.ancestor '[immutabledict ({})]
Supongo que probablemente necesite agregar algo a DbArea para establecer la relación, pero después experimentar con algunas opciones diferentes no pudo determinar la solución.