У меня есть следующая таблица 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)
Спасибо, если вы отправите ответ, я приму его.