SQLAlchemy:具有关系的混合表达式



我有两个Flask SQLAlchemy模型,它们具有简单的一对多关系,就像下面的最小示例:

class School(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30))
    address = db.Column(db.String(30))
class Teacher(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30))
    id_school = db.Column(db.Integer, db.ForeignKey(School.id))
    school = relationship('School', backref='teachers')

然后,我为使用关系的教师添加了一个混合属性,如下所示:

@hybrid_property
def school_name(self):
    return self.school.name

当我把它用作teacher_instance.school_name时,这个属性就可以正常工作了。然而,我也想进行类似Teacher.query.filter(Teacher.school_name == 'x')的查询,但这给了我一个错误:

`AttributeError: Neither 'InstrumentedAttribute' object nor 
'Comparator' object has an attribute 'school_name'`. 

根据SQLAlchemy文档,我添加了一个简单的混合表达式,如下所示:

@school_name.expression
def school_name(cls):
    return School.name

然而,当我再次尝试相同的查询时,它会生成一个不带联接子句的SQL查询,所以我会在School中获得所有可用行,而不仅仅是与Teacher中的外键匹配的行。

从SQLAlchemy文档中,我意识到表达式需要一个已经存在联接的上下文,所以我再次尝试了该查询,如下所示:

Teacher.query.join(School).filter(Teacher.school_name == 'x')

这实际上是有效的,但如果我需要学校模型的知识来获得语法糖的话,这就违背了我一开始就试图获得句法糖的目的。我希望有办法在表达式中加入,但我在任何地方都找不到。文档中有一个例子,其中的表达式返回了一个直接用select()构建的子查询,但这对我来说也不起作用

有什么想法吗?

更新

在Eevee给出下面的答案后,我按照建议使用了关联代理,它确实有效,但我也对它应该与select()子查询一起使用的评论感到好奇,并试图找出我做错了什么。我最初的尝试是:

@school_name.expression
def school_name(cls):
    return select(School.name).where(cls.id_school == School.id).as_scalar()

事实证明,这给了我一个错误,因为我错过了select((中的列表。下面的代码运行良好:

@school_name.expression
def school_name(cls):
    return select([School.name]).where(cls.id_school == School.id).as_scalar()

对于这样的简单情况,一种更简单的方法是关联代理:

class Teacher(db.Model):
    school_name = associationproxy('school', 'name')

这支持自动查询(至少使用==(。

我很好奇混合select()示例是如何不适用于您的,因为这是在混合中解决此问题的最简单方法。为了完成,您还可以使用转换器直接修改查询,而不是子查询。

最新更新