在 SQLAlchemy 中,当有多个关系时,如何访问查询中的引用属性?
Posted
技术标签:
【中文标题】在 SQLAlchemy 中,当有多个关系时,如何访问查询中的引用属性?【英文标题】:In SQLAlchemy, how to access a referenced property in a query when having multiple relationships? 【发布时间】:2012-09-26 16:04:20 【问题描述】:数据库结构
我有两个类,A 和 B,它们有两种不同的关系:
1) 多对多关系使用关联表(关联)来存储仅与该特定关联 (association_property_1) 相关的信息,并通过 A 和 B 中的反向引用进行实例化。
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)
程序
我想对所有关联运行查询,我可以通过与 B 的一对一关系访问 A 的特殊属性。所以基本上我希望能够访问该属性
B.a.a_property_special
在查询中。
特定查询的示例如下:
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)
其中 A 和 B 使用多对多关系连接,而 B.a 通过一对一连接。显然这个查询不起作用,因为 B 没有实例化,所以我无法访问 B.a.a_property_special。
如果我没有多对多关系,我可以在 B 上加入 A 并完成它。我的问题是我想使用关联查询 A 和 B,但我仍然需要通过一对一关系的标量 B.a.a_property_special。
可能的解决方案
我尝试了几种不同的解决方案,但由于各种原因都证明不令人满意。
将列“a_property_special”复制到表 B。我不喜欢这种做法,因为它会复制信息,并且如果 A 和 B 之间的一对一关系发生变化(可能在运行时发生这种情况),它不会提供良好的逻辑数据结构)。 使用 column_property 或 association_proxy。看起来很干净,但我只能让它在实例对象上正常工作。在查询中使用它们时,我在构建二进制表达式等时遇到问题。 使用子查询。我已经摆弄了一点,但一直无法生产出任何运作良好的东西。也许我只是做得不对,但它似乎总是以非常混乱和缓慢而告终。 只需查询所有关联并在 python 中执行数学、逻辑表达式和过滤。我的感觉是这会比在 SQL 中效率低,但我可能是错的..要求
它需要很快(呃)。我的表有好几次,每个表都有 100,000 条记录。 查询必须尽可能简单,以便易于调试和修改,同时仍能反映数据库的逻辑结构。我希望将尽可能多的代码隐藏在类定义中。 我没有对关系的结构有任何特别的偏好,我只需要一对一和多对多(包括它自己的关联属性)。李>我觉得这很简单,但我似乎无法找到一个好的解决方案。欢迎任何帮助或 cmets。
【问题讨论】:
【参考方案1】:SQLAlchemy 对连接是明确的,所以当你看到类似的东西时:
session.query(B).filter(B.a.a_property_special > 3.0)
真的是这个意思:
session.query(B).join(B.a).filter(A.a_property_special > 3.0)
还有一个子查询的情况,它不如连接有效。子查询的情况总是需要使用相关的子查询,像这样:
subq = session.query(A.a_property_special).where(A.id == B.a_id).correlate(B).as_scalar()
session.query(B).filter(subq > 3.0)
使用关系时,您还可以访问 any() 和 has() 方法,它们分别为一对多、多对一呈现 EXISTS 子查询:
session.query(B).filter(B.a.has(A.a_property_special > 3.0))
上面的等价于这个:
from sqlalchemy import exists
session.query(B).filter(exists().where(B.a_id==A.id, A.a_property_special > 3.0))
子查询的优点是它可以用来创建自包含的过滤条件,而当依赖 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)
您实际上是在尝试以两种不同的方式访问 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)
基本上,它的构建方式与您在 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
这里的子查询路由在数据库上会很困难。虽然您可以在关联上连接呈现子查询的属性,但它们都需要被称为相关子查询,如果您在一个查询中多次引用它们,这将表现得非常糟糕。以下是使用hybrid attributes 的方法:
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)
)
这里的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
为了查询的目的,数据库获得的关于这三个表中的行如何相互关联的信息较少,因此在获取行时它必须做更多的工作。连接案例,虽然它要求您以两种不同的方式将“A”作为目标,并指定事物的连接方式,但它为数据库提供了一项更简单的任务,因为连接比计算每行的相关 SELECT 的相关性更有效的父行集。
【讨论】:
非常感谢迈克!你解释它的方式很有意义。所以我猜我应该坚持使用连接来提高性能,尽管子查询在用户端看起来更干净。我想我曾希望有一种方法可以在类的属性中指定连接,而不必在查询时显式提供它。我不确定那将如何工作。我想显式连接的好处是你不太可能犯错误。 :)以上是关于在 SQLAlchemy 中,当有多个关系时,如何访问查询中的引用属性?的主要内容,如果未能解决你的问题,请参考以下文章