查询自引用List关系以检索多个级别的子级

时间:2014-07-16 11:00:26

标签: flask sqlalchemy querying

我有一个自引用的邻接列表关系,我创建了以下SqlAlchemy文档。该模型看起来像:

class Menu(db.Model):
    id          = db.Column(db.Integer, primary_key = True)
    title       = db.Column(db.String(255))
    ordering    = db.Column(db.SmallInteger, default = '1')
    parent_id   = db.Column(db.Integer, db.ForeignKey('menu.id'))
    children    = db.relationship("Menu",
                    cascade="all, delete-orphan",  
                    backref = db.backref('parent', remote_side=[id]),
                    collection_class = attribute_mapped_collection('id'))

我真正想要的是对该模型进行查询并获取如下数据:

root --+---> child1
       +---> child2 --+--> subchild1
       |              +--> subchild2--+--> and so on,
       |                               +--> and so on, if exists
       +---> child3 --+--> subchild1
                      +--> ...
                      +--> ...
                              +--> ...

将用以下数据表示:

id       parent_id     title
---      -------       ----
1        NULL          root
2        1             child1
3        1             child2
4        3             subchild1
5        3             subchild2
7        5             so_on1
8        5             so_on2
6        1             child3
9        6             subchild1

如何查询以检索上述表示的数据?

1 个答案:

答案 0 :(得分:6)

您可以使用resucrsive=True获取具有常量查询数的菜单项树,这将转换为SQL WITH RECURSIVE(在某些RDBMS上不可用)。如果树很大并且您想节省一些时间,这可能很有用。

# db - SQLA session

# pick a root of the menu tree
root = Menu.query.filter(Menu.parent_id == None).first()

# get ids of all menu items in tree with recursive query
included = db.query(
    Menu.id
).filter(
    Menu.parent_id == root.id
).cte(name="included", recursive=True)

included_alias = aliased(included, name="parent")
menu_alias = aliased(Menu, name="child")

included = included.union_all(
    db.query(
        menu_alias.id
    ).filter(
        menu_alias.parent_id == included_alias.c.id
    )
)

# include the root's id and extract ids from tuples
menu_ids = map(
    lambda _tuple: _tuple[0],
    [(root.id,)] + db.query(included.c.id).distinct().all(),
)

# fetch SQLA models
menus = Menu.query.filter(Menu.id.in_(menu_ids)).all()

如果您不需要速度,只需使用children关系在菜单项上运行DFS或BFS。