SQLAlchemy простой рекурсивный cte-запрос - PullRequest
0 голосов
/ 07 ноября 2018

У меня есть следующая таблица 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_franchisor | id_franchisee 
1 | 2
1 | 3
2 | 4
2 | 5
4 | 6

Тогда с учетом id 1 мне нужно 1,2,3,4,5,6, а с учетом 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 идентификатором, который я хочу. Я проверил это, и он отлично работает. Однако я хотел бы избежать использования жестко закодированного необработанного запроса, поэтому я пытаюсь воссоздать его в SQLAlchemy, но у меня не получается. Глядя на документацию в https://docs.sqlalchemy.org/en/latest/orm/query.html#sqlalchemy.orm.query.Query.cte Я придумал два альтернативных варианта, достаточно близких, но не совсем работающих.

Первое почти правильное, но я не знаю, как указать начальный идентификатор:

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?

EDIT:

После комментария Ильи Эвериля я смог найти следующее рабочее решение:

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)

Спасибо, если вы отправите ответ, я приму его.

...