Sqlalchemy 迫不及待地加载循环关联

Sqlalchemy 迫不及待地加载循环关联,sqlalchemy,Sqlalchemy,下面是多对多关联的一个最小示例。我的目标是加载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):

下面是多对多关联的一个最小示例。我的目标是加载
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)对查询进行处理(使用一些join和contains调用的组合)所以它知道如何实际使用它生成的连接,这样它就不会分解成n+1个查询

下面生成了正确的查询,但我无法从该查询中获取要加载的depth 2x实例(通过第二次选择延迟加载数据)


急切加载机制的工作方式是,您需要指定要加载的关系的路径以及加载方式。路径基本上是按照顺序遵循哪些关系,以便找到您想要的关系。在您的特定示例中,正确的做法是:

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),
           )
每个
包含\u eager
指定单个关系上的加载,路径(
X.ys,Z.y,y.xs,Z.X
)指定关系的位置,
包含\u eager
以及
别名
指定如何加载关系。这相当冗长,但幸运的是,SQLAlchemy提供了一个快捷方式,可以将它们链接在一起,如下所示:

.options(contains_eager(X.ys).contains_eager(Z.y).contains_eager(Y.xs, alias=a).contains_eager(Z.x, alias=b))
如果您使用
.join
的明确目标是执行
包含_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) \
           .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),
           )
每个
包含\u eager
指定单个关系上的加载,路径(
X.ys,Z.y,y.xs,Z.X
)指定关系的位置,
包含\u eager
以及
别名
指定如何加载关系。这相当冗长,但幸运的是,SQLAlchemy提供了一个快捷方式,可以将它们链接在一起,如下所示:

.options(contains_eager(X.ys).contains_eager(Z.y).contains_eager(Y.xs, alias=a).contains_eager(Z.x, alias=b))
如果您使用
.join
的明确目标是执行
包含_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
通常是一对多关系的正确选择(情况并非总是如此;取舍是在查询数量(即延迟)与每个查询中的数据量(即吞吐量)之间进行的,因此需要配置文件来确定):


最后,如果您只需要多对多关系,为什么不首先配置多对多关系?

Awesome-我的问题是,我只使用了第一个示例中的最后一个
contains\u eager
调用。我认为这就足够对路径进行编码了。太棒了-我的问题是,我只使用了第一个示例中的最后一个
contains\u eager
调用。我想这就足够对路径进行编码了。