急切地加载一个循环关联

问题描述:

我以下是多对多关联的一个简单例子。我的目标是要加载的X单个记录,也急切地加载它们在记录的ys列表以及这是在任何情况下这些公司xs名单的X实例的Y的实例。急切地加载一个循环关联

class X(db.Model): 
    __tablename__ = 'x' 
    xid = db.Column(db.Integer, primary_key=True) 
    ys = relationship('Z', back_populates='x', lazy='joined') 


class Y(db.Model): 
    __tablename__ = 'y' 
    yid = db.Column(db.Integer, primary_key=True) 
    xs = relationship('Z', back_populates='y', lazy='joined') 


class Z(db.Model): 
    __tablename__ = 'z' 
    xid = db.Column(db.Integer, db.ForeignKey('x.xid'), primary_key=True) 
    yid = db.Column(db.Integer, db.ForeignKey('y.yid'), primary_key=True) 
    x = relationship('X', back_populates='ys', lazy='joined') 
    y = relationship('Y', back_populates='xs', lazy='joined') 

我的目标是产生如下结果:

expected = [{ 
    'xid': 1, 
    'ys': [ 
     {'yid': 101, 'xs': [{'xid': 1}, {'xid': 2}, {'xid': 3}]}, 
     {'yid': 102, 'xs': [{'xid': 1}, {'xid': 2}]}, 
     {'yid': 104, 'xs': [{'xid': 1}, {'xid': 4}]}, 
    ], 
}] 

的SQL语句来实现,这是相当简单:

SELECT x.xid, y.yid, x2.xid FROM x 
JOIN z  ON z.xid = x.xid JOIN y  ON z.yid = y.yid ; Fetch Ys 
JOIN z as z2 ON z2.yid = y.yid JOIN x as x2 ON z2.xid = x2.xid ; Fetch Xs (depth 2) 
WHERE x.xid = 1 

我的问题是确定如何创建SQLAlchemy的查询(a)允许我执行这个原始查询并将其正确映射到正确的模型实例,或者(b)按照查询(使用join和contains_eager调用的某种组合s),以便它知道如何使用生成的连接,以便它不会分解为n + 1个查询。

正确的查询是由以下生成的,但我无法设法从这个查询中加载深度2个X实例(数据是由第二个选择器懒洋洋地加载的)。

a = aliased(Z) 
b = aliased(X) 
q = X.query.filter(X.xid==1).join(X.ys).join(Z.y).join(a, Y.xs).join(b, Z.x) 

热切加载机制的工作方式是,你需要指定要和加载,只要你想如何加载它的关系路径。路径基本上是按顺序跟随的关系,以便找到你想要的关系。在您的具体的例子,做正确的事情是这样的:

q = session.query(X).filter(X.xid == 1) \ 
      .join(X.ys) \ 
      .join(Z.y) \ 
      .join(a, Y.xs) \ 
      .join(b, Z.x) \ 
      .options(
       contains_eager(X.ys), 
       contains_eager(X.ys, Z.y), 
       contains_eager(X.ys, Z.y, Y.xs, alias=a), 
       contains_eager(X.ys, Z.y, Y.xs, Z.x, alias=b), 
      ) 

每个contains_eager指定单个关系的负荷,与指定路径(X.ys, Z.y, Y.xs, Z.x)式的关系,和contains_eager以及alias指定如何加载关系。这是相当冗长,但幸运的SQLAlchemy提供了一条捷径,让你把它们连在一起,就像这样:

.options(contains_eager(X.ys).contains_eager(Z.y).contains_eager(Y.xs, alias=a).contains_eager(Z.x, alias=b)) 

如果您使用.join对于然后做contains_eager明确的目标,你可能也只是使用joinedload而不是:

q = session.query(X).filter(X.xid==1) \ 
      .options(joinedload(X.ys).joinedload(Z.y).joinedload(Y.xs).joinedload(Z.x)) 

你的具体情况,加入这样可以不,如果你的分支系数高是有效的,也就是说,如果你的X.ysY.xs包含最多n条目,那么你的数据库中有你发副本X中的每一行都是。出于这个原因,subqueryload通常是一对多关系的正确选择(情况并非总是如此;折衷在查询的数量即延迟与每个查询中的数据量(即吞吐量)之间,所以轮廓找出):

q = session.query(X).filter(X.xid==1) \ 
      .options(subqueryload(X.ys).joinedload(Z.y).subqueryload(Y.xs).joinedload(Z.x)) 

最后,如果你想要的是一个多一对多的关系,为什么不直接配置在首位很多一对多的关系?

+0

太棒了 - 我的问题是,我只使用_last_'contains_eager'调用,你在第一个例子中。我认为这足以对路径进行编码。 – efritz