以下是多对多关联的最小示例。我的目标是加载X
的单个记录,并急切加载Y
列表中列出的ys
个实例以及 X
的实例,这些实例位于任何这些实例的xs
列表中。
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)按摩查询(使用连接和contains_eager调用),以便它知道如何实际使用它生成的连接,这样它就不会爆炸成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)
答案 0 :(得分:4)
预先加载机制的工作方式是,您需要为要加载的关系指定路径,以及如何加载它。路径基本上是依次遵循哪些关系,以便找到您想要的关系。在您的特定示例中,正确的做法是:
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.ys
和Y.xs
包含最多n
条目,则此类加入可能效率不高,那么您的数据库必须向您发送<{>> X 中每一行的n^2
份副本。出于这个原因,subqueryload
通常是一对多关系的正确选择(情况并非总是如此;权衡取决于查询数量,即延迟与每个查询中的数据量之间的关系,即吞吐量,所以剖析要找出来:
q = session.query(X).filter(X.xid==1) \
.options(subqueryload(X.ys).joinedload(Z.y).subqueryload(Y.xs).joinedload(Z.x))
最后,如果您想要的只是多对多的关系,为什么不首先配置多对多关系呢?