I recently started working with SQL Alchemy for a project that
involves climbing areas and routes. Areas are hierarchical in that a
single area may contain multiple areas, which in turn may contain
other areas. A route is directly associated with a single area, but is
also associated with that area's parent, etc.

To implement this I chose to use a closure table ala Bill Karwin
(http://karwin.blogspot.com/2010/03/rendering-trees-with-closure-
tables.html). In the closure table implementation, a second table is
created to store the ancestor/descendent information. A self-
referencing row is created when a node is added, as well as a row for
each ancestor in the tree.


The table structure is as follows (simplified):

-- area --
area_id
name

-- area_relationship --
ancestor
descendent

-- route --
route_id
area_id
name


Sample data:

-- 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


To query for all areas for a given route (up the tree), I can execute:

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
Similarly, I can query for all routes in a particular area (including
descendent areas) with:

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


In SQL Alchemy I've created a relationship and two tables to handle
these relationships:

area_relationship_table = Table('area_relationship', Base.metadata,
  Column('ancestor', Integer, ForeignKey('area.area_id')),
  Column('descendent', Integer, ForeignKey('area.area_id'))
)


DbArea class -

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 -

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])


Currently, I am able to determine the areas from the individual route,
using the areas relationship in DbRoute. However, when I try to use
the backref 'routes' in DbArea, I get the following error:

sqlalchemy.exc.StatementError: No column route.area_id is configured
on mapper Mapper|DbArea|area... (original cause: UnmappedColumnError:
No column route.area_id is configured on mapper Mapper|DbArea|area...)
'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({})]

I'm guessing that I likely need to add something to DbArea to
establish the relationship, but after experimenting with some
different options was unable to determine the solution.

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalchemy@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

Reply via email to