SQLAlchemy简单递归CTE查询

时间:2018-11-07 09:13:49

标签: python sqlalchemy common-table-expression recursive-query

我有以下SQLAlchemy表:

from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class NetworkLink(Base):
    """Network immediate link between a franchisee and his franchisor

    """
    __tablename__ = 'network_link'

    id_franchisee = Column(Integer, ForeignKey('user.id'), primary_key=True)
    id_franchisor = Column(Integer, ForeignKey('user.id'))

基本上代表了树状网络结构。

鉴于特许人的ID,我需要获取整个子树中所有后代的ID。 例如,表如下:

id_franchisor | id_franchisee 
1 | 2
1 | 3
2 | 4
2 | 5
4 | 6

然后给定id 1,我需要1,2,3,4,5,6,而给定id 2,我需要2,4,5,6。

我知道这不是解决此问题的最有效的表表示形式,但是该操作很少执行,并且插入操作更为常见。

我正在尝试通过递归查询来实现这一点,该查询看起来像这样:

"""
WITH RECURSIVE recursive_franchisee(id) AS
(
    SELECT %s
    UNION ALL
    SELECT L.id_franchisee
    FROM recursive_franchisee as R JOIN network_link as L ON R.id = L.id_franchisor
) SELECT id FROM recursive_franchisee;
"""

我可以在其中用%s代替我想要的ID。我已经对其进行了测试,并且效果很好。 但是,我想避免使用硬编码的原始查询,因此我试图在SQLAlchemy中重新创建它,但是失败了。 查看中的文档 https://docs.sqlalchemy.org/en/latest/orm/query.html#sqlalchemy.orm.query.Query.cte 我想出了两个替代方案,它们足够接近但不能完全正常工作。

第一个几乎是正确的,但我不知道如何指定起始ID:

rec = db_session.query("id").cte(recursive=True, name="recursive_franchisee")
ralias = sqlalchemy.orm.aliased(rec, name="R")
lalias = sqlalchemy.orm.aliased(NetworkLink, name="L")
rec = rec.union_all(
    db_session.query(lalias.id_franchisee) \
              .join(ralias, ralias.c.id == lalias.id_franchisor)
)
qr = db_session.query(rec)
sqr = str(qr)

# sqr equals to:
"""
WITH RECURSIVE recursive_franchisee(id) AS
(
    SELECT id  # how do I specify an id here?
    UNION ALL
    SELECT "L".id_franchisee AS "L_id_franchisee"
    FROM network_link AS "L" JOIN recursive_franchisee AS "R" ON "R".id = "L".id_franchisor
)
SELECT recursive_franchisee.id FROM recursive_franchisee
"""

我还尝试了以下操作,但有一个不同的问题:

rec = db_session.query(NetworkLink.id_franchisor) \
                .filter(NetworkLink.id_franchisor == 1) \
                .cte(recursive=True, name="recursive_franchisee")
ralias = sqlalchemy.orm.aliased(rec, name="R")
lalias = sqlalchemy.orm.aliased(NetworkLink, name="L")
rec = rec.union_all(
    db_session.query(lalias.id_franchisee) \
              .join(ralias, ralias.c.id_franchisor == lalias.id_franchisor)
)
qr = db_session.query(rec)
sqr = str(qr)

# sqr equals to:
"""
WITH RECURSIVE recursive_franchisee(id_franchisor) AS
(
    SELECT network_link.id_franchisor AS id_franchisor
    FROM network_link
    WHERE network_link.id_franchisor = ?
    UNION ALL
    SELECT "L".id_franchisee AS "L_id_franchisee"
    FROM network_link AS "L" JOIN recursive_franchisee AS "R" ON "R".id_franchisor = "L".id_franchisor
)
SELECT recursive_franchisee.id_franchisor AS recursive_franchisee_id_franchisor
FROM recursive_franchisee
"""

这当然会产生重复的结果,因为前一个SELECT会多次返回特许人(每个特许人一个)。我可以使用DISTINCT,但我想避免选择一开始就已经知道的东西。

如何在SQLAlchemy中实现所需的查询?

编辑:

在IljaEverilä的评论之后,我提出了以下可行的解决方案:

from sqlalchemy.sql.expression import literal

rec = db_session.query(literal(current_user.id).label("id")) \
                .cte(recursive=True, name="recursive_franchisee")
ralias = sqlalchemy.orm.aliased(rec, name="R")
lalias = sqlalchemy.orm.aliased(NetworkLink, name="L")
rec = rec.union_all(
    db_session.query(lalias.id_franchisee) \
            .join(ralias, ralias.c.id == lalias.id_franchisor)
)
qr = db_session.query(rec)

谢谢,如果您发布答案,我会接受的。

0 个答案:

没有答案