在SQLAlchemy中,如何在具有多个关系时访问查询中的引用属性?

ask*_*boe 2 python sql database orm sqlalchemy

数据库结构

我有两个类,A和B,它们有两种不同的关系:

1)使用关联表(关联)来存储仅与该特定关联相关的信息(association_property_1)并通过A和B中的backref实例化的多对多关系.

2)使用table_b中的外键,A和B之间的一对一关系,这样只有B'知道'这种关系.我不在乎A是否知道它,但这样看起来似乎更简单.

我的课程看起来像这样:

class A(Base):
  __tablename__ = 'table_a'

  id = Column(Integer, primary_key=True)
  a_property_1 = Column(Float)
  a_property_2 = Column(Float)
  a_property_special = Column(Float)

  # Many-to-many relationship with B through an Association
  associated_bs = relationship('Association', backref='a')

class B(Base):
  __tablename__ = 'table_b'

  id = Column(Integer, primary_key=True)
  b_property_1 = Column(Float)
  b_property_2 = Column(Float)

  # One-to-one relationship with A
  a_id = Column(Integer, ForeignKey('table_a.id'))
  a = relationship('A', uselist=False, backref='b')

  # Many-to-many relationship with A through an Association
  associated_as = relationship('Association', backref='b')

class Association(Base):
  __tablename__ = 'associations'

  a_id = Column(Integer, ForeignKey('table_a.id'), primary_key=True)
  b_id = Column(Integer, ForeignKey('table_b.id'), primary_key=True)

  association_property_1 = Column(Float)
Run Code Online (Sandbox Code Playgroud)

程序

我想对所有关联运行查询,我可以通过与B的一对一关系访问A的特殊属性.所以基本上我希望能够访问该属性

B.a.a_property_special
Run Code Online (Sandbox Code Playgroud)

查询中.

特定查询的示例可能如下:

session.query(Association.association_property_1,
  func.abs(A.a_property_special - B.a.a_property_special).\
  filter(B.a.a_property_special > 3.0)
Run Code Online (Sandbox Code Playgroud)

其中A和B使用多对多关系连接,Ba通过一对一连接.显然这个查询不起作用,因为B没有实例化,所以我将无法访问Baa_property_special.

如果我没有多对多的关系,我可以加入A对B并完成它.我的问题是我想使用关联查询A和B,但我仍然需要通过一对一关系的标量Baa_property_special.

可能的解决方案

我已经尝试了几种不同的解决方案,但由于各种原因,所有这些解决方

  • 将列'a_property_special'复制到表B.这我不喜欢,因为它复制了信息,并且如果A和B之间的一对一关系发生变化(它可能在运行时),它不会呈现出良好的逻辑数据结构.
  • 使用column_property或association_proxy.看起来很干净,但我只能让它在实例化对象上正常工作.在查询中使用它们时,我遇到构造二进制表达式等问题.
  • 使用子查询.我已经摆弄了一下,但是还没能生产出效果很好的东西.也许我只是做得不对,但似乎总是变得非常混乱和缓慢.
  • 只需查询所有关联,并在python中进行数学,逻辑表达式和过滤.我的感觉是,这比在SQL中执行它效率低,但我可能是错的..

要求

  • 它需要快速(duh).我的桌子每次有几十万条记录.
  • 查询必须尽可能简单,以便于调试和修改,同时仍然反映数据库的逻辑结构.我宁愿保留尽可能多的代码隐藏在类定义中.
  • 我对关系的结构没有任何特别的偏好,我只需要一对一和多对多(包括它自己的相关属性).

我觉得这很简单,但我似乎无法找到一个好的解决方案.欢迎任何帮助或评论.

zzz*_*eek 6

SQLAlchemy明确关于连接,所以当你看到类似的东西时:

session.query(B).filter(B.a.a_property_special > 3.0)
Run Code Online (Sandbox Code Playgroud)

这真的意味着:

session.query(B).join(B.a).filter(A.a_property_special > 3.0)
Run Code Online (Sandbox Code Playgroud)

还有一个子查询案例,它不如连接有效.子查询案例总是需要使用相关的子查询,如下所示:

subq = session.query(A.a_property_special).where(A.id == B.a_id).correlate(B).as_scalar()
session.query(B).filter(subq > 3.0)
Run Code Online (Sandbox Code Playgroud)

使用关系时,您还可以访问any()和has()方法,这些方法分别为一对多,多对一呈现EXISTS子查询:

session.query(B).filter(B.a.has(A.a_property_special > 3.0))
Run Code Online (Sandbox Code Playgroud)

以上就相当于:

from sqlalchemy import exists

session.query(B).filter(exists().where(B.a_id==A.id, A.a_property_special > 3.0))
Run Code Online (Sandbox Code Playgroud)

子查询的优点是它可以用于创建自包含的过滤条件,而当依赖于join()时,没有办法隐式发生.但是子查询方法在数据库方面表现不佳.

当然有很多简单的情况,可以根据存在的各种事物将连接隐式添加到封闭查询中,这就像Django那样的ORM,但是SQLAlchemy对它的看法是,你很快就会遇到一个案例的海洋.简单的方法就像破坏了,所以我们不要在库中做出类似的猜测.

所以要采用原始查询示例:

session.query(Association.association_property_1,
  func.abs(A.a_property_special - B.a.a_property_special)).\
  filter(B.a.a_property_special > 3.0)
Run Code Online (Sandbox Code Playgroud)

你实际上试图用两种不同的方式来命中A,所以在进行显式连接路由时,你需要为它做一个别名,以便它可以被定位两次:

from sqlalchemy.orm import aliased
a_alias = aliased(A)
session.query(
      Association.association_property_1,
      func.abs(A.a_property_special - a_alias.a_property_special)
     ).\
     join(Association.a).\
     join(Association.b).join(a_alias, B.a).\
     filter(a_alias.a_property_special > 3.0)
Run Code Online (Sandbox Code Playgroud)

这基本上和你在SQL中使用它的方式相同.SQL是这样的:

SELECT associations.association_property_1 AS associations_association_property_1, abs(table_a.a_property_special - table_a_1.a_property_special) AS abs_1 
FROM associations JOIN table_a ON table_a.id = associations.a_id JOIN table_b ON table_b.id = associations.b_id JOIN table_a AS table_a_1 ON table_a_1.id = table_b.a_id 
WHERE table_a_1.a_property_special > :a_property_special_1
Run Code Online (Sandbox Code Playgroud)

这里的子查询路由在数据库上很难.虽然你可以连接渲染子查询的Association上的属性,但它们都需要被称为相关子查询,如果你在一个查询中多次引用它们,它们的执行情况会非常糟糕.以下是使用混合属性的方法:

class Association(Base):
    __tablename__ = 'associations'

    a_id = Column(Integer, ForeignKey('table_a.id'), primary_key=True)
    b_id = Column(Integer, ForeignKey('table_b.id'), primary_key=True)

    association_property_1 = Column(Float)

    @hybrid.hybrid_property
    def a_property_special(self):
        return self.a.a_property_special

    @a_property_special.expression
    def a_property_special(cls):
        return select([A.a_property_special]).where(A.id==cls.a_id).as_scalar()

    @hybrid.hybrid_property
    def b_a_property_special(self):
        return self.b.a.a_property_special

    @b_a_property_special.expression
    def b_a_property_special(cls):
        return select([A.a_property_special]).where(A.id==B.a_id).where(B.id==cls.b_id).as_scalar()

session.query(
  Association.association_property_1,
  func.abs(Association.a_property_special - Association.b_a_property_special)
 )
Run Code Online (Sandbox Code Playgroud)

这里的SQL是:

SELECT associations.association_property_1 AS associations_association_property_1, abs((SELECT table_a.a_property_special 
FROM table_a 
WHERE table_a.id = associations.a_id) - (SELECT table_a.a_property_special 
FROM table_a, table_b 
WHERE table_a.id = table_b.a_id AND table_b.id = associations.b_id)) AS abs_1 
FROM associations
Run Code Online (Sandbox Code Playgroud)

为了这个查询的目的,数据库给出的关于这三个表中的行如何相互关联的信息较少,因此在获取行时它必须做更多的工作.连接情况,虽然它要求您以两种不同的方式将"A"布局为目标,并且还指定事物如何连接,为数据库提供更简单的任务,因为连接比计算每行的相关SELECT的相关性更有效父行集.