contains_eager and limits in SQLAlchemy

拈花ヽ惹草 提交于 2021-01-28 04:06:07

问题


I have 2 classes:

class A(Base):
    id = Column(Integer, primary_key=True)
    name = Column(String)
    children = relationship('B')
class B(Base):
    id = Column(Integer, primary_key=True)
    id_a = Column(Integer, ForeignKey('a.id'))
    name = Column(String)

Now I need all object A which contains B with some name and A object will contain all B objects filtered.

To achieve it I build query.

query = db.session.query(A).join(B).options(db.contains_eager(A.children)).filter(B.name=='SOME_TEXT')

Now I need only 50 items of query so I do:

query.limit(50).all()

Result contain less then 50 even if without limit there is more than 50. I read The Zen of Eager Loading. But there must be some trick to achieve it. One of my idea is to make 2 query. One with innerjoin to take ID's then use this ID's in first query.

But maybe there is better solve for this.


回答1:


First, take a step back and look at the SQL. Your current query is

SELECT * FROM a JOIN b ON b.id_a = a.id WHERE b.name == '...' LIMIT 50;

Notice the limit is on a JOIN b and not a, but if you put the limit on a you can't filter by the field in b. There are two solutions to this problem. The first is to use a scalar subquery to filter on b.name, like this:

SELECT * FROM a
WHERE EXISTS (SELECT 1 FROM b WHERE b.id_a = a.id AND b.name = '...')
LIMIT 50;

This can be inefficient depending on the DB backend. The second solution is to do a DISTINCT on a after the join, like this:

SELECT DISTINCT a.* FROM a JOIN b ON b.id_a = a.id
WHERE b.name == '...'
LIMIT 50;

Notice how in either case you do not get any column from b. How do we get them? Do another join!

SELECT * FROM (
    SELECT DISTINCT a.* FROM a JOIN b ON b.id_a = a.id
    WHERE b.name == '...'
    LIMIT 50;
) a JOIN b ON b.id_a = a.id
WHERE b.name == '...';

Now, to write all of this in SQLAlchemy:

subquery = (
    session.query(A)
           .join(B)
           .with_entities(A)  # only select A's columns
           .filter(B.name == '...')
           .distinct()
           .limit(50)
           .subquery()  # convert to subquery
)
aliased_A = aliased(A, subquery)
query = (
    session.query(aliased_A)
           .join(B)
           .options(contains_eager(aliased_A.children))
           .filter(B.name == "...")
)


来源:https://stackoverflow.com/questions/41900315/contains-eager-and-limits-in-sqlalchemy

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!