6

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.

Daniele
  • 153
  • 3
  • 11
  • The first is answered here: [How do I select literal values in an sqlalchemy query](https://stackoverflow.com/questions/7533146/how-do-i-select-literal-values-in-an-sqlalchemy-query), the second seems like a way to do almost the same (with the exception that it can result in an empty result set). – Ilja Everilä Nov 07 '18 at 11:00

0 Answers0