Python sqlalchemy混合属性表达式

Python sqlalchemy混合属性表达式,python,sqlalchemy,flask-sqlalchemy,Python,Sqlalchemy,Flask Sqlalchemy,假设以下模型: class Worker(Model): __tablename__ = 'workers' ... jobs = relationship('Job', back_populates='worker', order_by='desc(Job.started)', lazy='dynamic') @hyb

假设以下模型:

class Worker(Model):
    __tablename__ = 'workers'
    ...
    jobs = relationship('Job',
                        back_populates='worker',
                        order_by='desc(Job.started)',
                        lazy='dynamic')

    @hybrid_property
    def latest_job(self):
        return self.jobs.first()  # jobs already ordered descending

    @latest_job.expression
    def latest_job(cls):
        Job = db.Model._decl_class_registry.get('Job')
        return select([func.max(Job.started)]).where(cls.id == Job.worker_id).as_scalar()

class Job(Model):
    ...
    started = db.Column(db.DateTime, default=datetime.utcnow)
    worker_id = db.Column(db.Integer, db.ForeignKey('workers.id'))
    worker = db.relationship('Worker', back_populates='jobs')
虽然此查询提供了正确的结果:

db.session.query(Worker).join(Job.started).filter(Job.started >= datetime.datetime(2017, 5, 10, 0, 2, 45, 932983)).distinct().count()
我假设可以直接查询该字段,但此查询失败:

db.session.query(Worker).join(Job).filter(Worker.latest_job.started >= datetime.datetime(2017, 5, 10, 0, 2, 45, 932983)).count()
出现此错误时:

AttributeError: Neither 'hybrid_property' object nor 'ExprComparator' object associated with Worker.latest_job has an attribute 'started'
TypeError: '>=' not supported between instances of 'Select' and 'datetime.datetime'
如何直接查询此属性?我错过了什么

编辑1: 根据@Ilja的回答,我尝试:

db.session.query(Worker).\
    join(Job).\
    filter(Worker.latest_job >= datetime.datetime(2017, 5, 10, 0, 2, 45, 932983)).\
    count()
但是得到这个错误:

AttributeError: Neither 'hybrid_property' object nor 'ExprComparator' object associated with Worker.latest_job has an attribute 'started'
TypeError: '>=' not supported between instances of 'Select' and 'datetime.datetime'

在SQL(类)上下文中使用时,您将从混合属性返回标量子查询,因此只需像使用值表达式一样使用它:

db.session.query(Worker).\
    filter(Worker.latest_job >= datetime.datetime(2017, 5, 10, 0, 2, 45, 932983)).\
    count()
在这种情况下,hybrid属性本身需要显式处理相关性:

@latest_job.expression
def latest_job(cls):
    Job = db.Model._decl_class_registry.get('Job')
    return select([func.max(Job.started)]).\
        where(cls.id == Job.worker_id).\
        correlate(cls).\
        as_scalar()
请注意,混合属性的Python端和SQL端之间存在一些不对称性。在实例上访问时,它生成最新的
Job
对象,而在SQL中生成相关的标量子查询
max(start)
。如果希望它在SQL中也返回
作业
行,可以执行以下操作

@latest_job.expression
def latest_job(cls):
    Job = db.Model._decl_class_registry.get('Job')
    return Job.query.\
        filter(cls.id == Job.worker_id).\
        order_by(Job.started.desc()).\
        limit(1).\
        correlate(cls).\
        subquery()
但这实际上没有多大用处,因为通常(但并非总是)这种相关子查询比对子查询进行连接要慢。例如,为了获取具有满足原始条件的最新作业的工人:

job_alias = db.aliased(Job)
# This reads as: find worker_id and started of jobs that have no matching
# jobs with the same worker_id and greater started, or in other words the
# worker_id, started of the latest jobs.
latest_jobs = db.session.query(Job.worker_id, Job.started).\
    outerjoin(job_alias, and_(Job.worker_id == job_alias.worker_id,
                              Job.started < job_alias.started)).\
    filter(job_alias.id == None).\
    subquery()

db.session.query(Worker).\
    join(latest_jobs, Worker.id == latest_jobs.c.worker_id).\
    filter(latest_jobs.c.started >= datetime.datetime(2017, 5, 10, 0, 2, 45, 932983)).\
    count()
job\u alias=db.aliased(作业)
#其内容为:查找没有匹配项的工作的工单id和开始数
#已启动具有相同工号和更大工号的作业,或者换句话说
#工人id,已开始最新作业的编号。
latest_jobs=db.session.query(Job.worker_id,Job.started)\
outerjoin(job\u别名)和(job.worker\u id==job\u别名.worker\u id,
Job.started=datetime.datetime(2017,5,10,0,2,45932983))\
计数()
当然,如果您只需要计数,那么您根本不需要联接:

job_alias = db.aliased(Job)
db.session.query(func.count()).\
    outerjoin(job_alias, and_(Job.worker_id == job_alias.worker_id,
                              Job.started < job_alias.started)).\
    filter(job_alias.id == None,
           Job.started >= datetime.datetime(2017, 5, 10, 0, 2, 45, 932983)).\
    scalar()
job\u alias=db.aliased(作业)
db.session.query(func.count())\
outerjoin(job\u别名)和(job.worker\u id==job\u别名.worker\u id,
Job.started=datetime.datetime(2017,5,10,0,2,45932983))\
标量()

请注意,对的调用与不同,只返回第一行的第一个值。

您不必这样做。标量子查询(可用作标量值)的定义是,它是一个1行1列的表。SQL这样很有趣,我希望它不是那么“神奇”。关于相关性问题,只需将
correlate(cls)
添加到
select()
构造中,在
as\u scalar()
之前。例如:
select 1=(select 1)。在SQL中(几乎)一切都是一个表。事实上,比较
=
是按行定义的,当你说
1=1
时,它实际上意味着
(1)=(1)
,或者换句话说,将这一行1列与另一行1列进行比较。在标准中,A被定义为
:=
,其中
:=
。从技术上讲
session.query(工人。最近的工作开始了)。全部()
生成结果元组列表。它不是…列的1元组的原因是SQL将标量子查询视为一个值。通常您会使用合适的联接。修复了您注意到的ORM和核心样式的混乱。如果您得到
类型错误:'>='在'Select'和'datetime.datetim'实例之间不受支持e'
,您已删除对
as_scalar()的调用
@IljaEverilä在深入研究之后,我看到了您试图向我展示的内容。来自Django的我期望某些行为不会像SA那样映射到SQL。您的回答很有帮助,我想选择它,但它已被删除。请重新发布。@IljaEverilä是子区中允许返回的唯一类型的标量值eries?否,例如,您可以将构造的行与子查询的结果进行比较,或者如果在
谓词中使用
,则可以将结果进行比较。此外,在
FROM
子句中使用子查询来生成派生表也是很常见的。