【问题标题】:Query self-referential List relationship to retrieve several level child查询自引用列表关系以检索多级子级
【发布时间】:2019-10-08 02:21:36
【问题描述】:

我有一个自引用邻接列表关系,它是我根据 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

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

【问题讨论】:

    标签: python sqlalchemy


    【解决方案1】:

    您可以使用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。

    【讨论】:

    • 感谢您的回复和帮助。你的方式似乎真的很专业。唯一的问题是它给出了错误 AttributeError: 'NoneType' object has no attribute 'id' filter(Menu.parent_id == root.id)
    • 似乎Menu.query.filter(Menu.parent_id == None).first() 返回了None,它被赋值为root - 你的数据库中可能没有任何没有父母的菜单项(这意味着你没有有的话,我希望)。
    • 事实上问题在于 None 声明就像'None'Menu.query.filter(Menu.parent_id == 'None').first()。我修好了。
    • 很抱歉耽误您的时间,但现在它引发了另一个错误:sqlalchemy.exc.OperationalError OperationalError: (OperationalError) near "WITH": syntax error u'WITH RECURSIVE included(id) AS \n(SELECT menu.id AS id \nFROM menu \nWHERE menu.parent_id = ? UNION ALL SELECT child.id AS child_id \nFROM menu AS child, included AS parent \nWHERE child.parent_id = parent.id)\n SELECT DISTINCT included.id AS included_id \nFROM included' (1,) 由于我是这个环境的新手,所以我对此一无所知
    • 我不认为 'None' 而不是 None 会起作用 - 也许这就是导致语法错误的原因。另一种可能是您的数据库服务器不支持WITH RECURSIVE - 我只将它与 Postgresql 一起使用。
    猜你喜欢
    • 1970-01-01
    • 2020-08-27
    • 2020-11-29
    • 1970-01-01
    • 1970-01-01
    • 2022-01-08
    • 2012-06-28
    • 1970-01-01
    • 2016-04-20
    相关资源
    最近更新 更多