代码之家  ›  专栏  ›  技术社区  ›  efritz

急切地加载循环关联

  •  0
  • efritz  · 技术社区  · 7 年前

    以下是多对多关联的一个最小示例。我的目标是加载 X 并急切地加载 Y ys 列表 以及 实例 十、 在这些实例中的任何一个 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\u 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)
    
    1 回复  |  直到 7 年前
        1
  •  4
  •   univerio    7 年前

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

    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 )指定关系所在的位置,以及 包含_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 为了明确的目标 包含_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 条目,则您的数据库必须向您发送 n^2 副本 X中的每一行 . 因此, subqueryload 通常是一对多关系的正确选择(情况并非总是如此;取舍是在查询数量(即延迟)与每个查询中的数据量(即吞吐量)之间进行的,因此需要进行分析以找出原因):

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

    最后,如果您想要的只是一个多对多关系,为什么不首先配置一个多对多关系呢?