2012-01-07 23 views
6

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 -

clase
class 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.

Respuesta

6

Después de la publicación de la Alquimia Google Grupo de SQL y recibir alguna awesome help from Michael Bayer, llegué a la siguiente definición de la relación zonas en la clase DbRoute

areas = relationship('DbArea', 
    backref = backref('routes', order_by = 'DbRoute.name'), 
    secondary = area_relationship_table, 
    primaryjoin = area_id == area_relationship_table.c.descendent, 
    secondaryjoin = DbArea.area_id == area_relationship_table.c.ancestor, 
    innerjoin = True, order_by = DbArea.name, 
    foreign_keys = 
     [area_relationship_table.c.ancestor, 
      area_relationship_table.c.descendent]) 

La clave estaba realmente en la definición de los puntos de unión correctamente. Ahora puedo ir fácilmente desde una instancia de ruta y encontrar áreas en el árbol antecesor, o desde un área y encontrar todas las rutas en el árbol descendiente.