I have the following SQLAlchemy table:
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'))
Which basically represent a tree-like network structure.
Given the id of a franchisor, I need to get the ids of all the descendants in the entire subtree. For instance if the table were as follows:
id_franchisor | id_franchisee
1 | 2
1 | 3
2 | 4
2 | 5
4 | 6
Then given id 1 I need 1,2,3,4,5,6 while given 2 I need 2,4,5,6.
I am aware that this is not the most efficient table representation for this issue, but this operation will be executed rarely and insertions will be much more common.
I am trying to implement this with a recursive query, which would look like this:
"""
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;
"""
Where I can substitute %s
for the id I want. I've tested it and it works fine.
However I would like to avoid using a hardcoded raw query, so I'm trying to recreate it in SQLAlchemy but I'm failing.
Looking at the documentation in
https://docs.sqlalchemy.org/en/latest/orm/query.html#sqlalchemy.orm.query.Query.cte
I came up with two alternaties, close enough but not quite working.
The first is almost correct but I don't know how to specify the starting 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
"""
I also tried the following, that has a different problem:
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
"""
This of course generates duplicate results because the first SELECT
returns the franchisor multiple times (one for each franchisee). I could use DISTINCT
I suppose but I would like to avoid selecting for something I already know in the first place.
How can I implement the query I need in SQLAlchemy?
EDIT:
Following Ilja Everilä's comment, I was able to come up with the following working solution:
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)
Thank you, if you post an answer I will accept it.