[英]How do I define a SQLAlchemy relation representing the latest object in a collection?
I have a SQLAlchemy model with a one-to-many relationship between table x
and table y
.我有一个 SQLAlchemy model 与表x
和表y
之间的一对多关系。 The record (if any) with the greatest id
in table y
where y.x_id = x.id
is special.表y
中y.x_id = x.id
的id
最大的记录(如果有)是特殊的。 Class X
and class Y
map tables x
and y
. Class X
和 class Y
map 表x
和y
。
I know how to define X.all_y
( ORDER BY y.id
).我知道如何定义X.all_y
( ORDER BY y.id
)。 How do I define X.latest_y
equivalent to X.all_y[-1]
?如何定义X.latest_y
等同于X.all_y[-1]
?
the purely relational way to do it requires using a subquery to get the "latest" or "max" value, correlated to the parent, then equating that with the members of the collection. 纯粹的关系方式要求使用子查询来获取与父级相关的“最新”或“最大”值,然后将其与集合的成员等同。 It means you'll get best results if you put an index on the column that determines "the latest": 这意味着如果您在确定“最新”的列上放置索引,您将获得最佳结果:
from sqlalchemy import *
from sqlalchemy.orm import *
engine = create_engine('sqlite:///:memory:', echo='debug')
m = MetaData()
parent = Table('parent', m,
Column('id', Integer, primary_key=True)
)
child = Table('child', m,
Column('id', Integer, primary_key=True),
Column('parent_id', Integer, ForeignKey('parent.id')),
Column('sortkey', Integer)
)
m.create_all(engine)
class Parent(object):
def __init__(self, children):
self.all_c = children
class Child(object):
def __init__(self, sortkey):
self.sortkey = sortkey
latest_c = select([func.max(child.c.sortkey)]).\
where(child.c.parent_id==parent.c.id).\
correlate(parent).\
as_scalar()
mapper(Parent, parent, properties={
'all_c':relation(Child),
'latest_c':relation(Child,
primaryjoin=and_(
child.c.sortkey==latest_c,
child.c.parent_id==parent.c.id
),
uselist=False
)
})
mapper(Child, child)
session = sessionmaker(engine)()
p1, p2, p3 = Parent([Child('a'), Child('b'), Child('c')]), \
Parent([Child('b'), Child('c')]),\
Parent([Child('f'), Child('g'), Child('c')])
session.add_all([p1, p2, p3])
session.commit()
assert p1.latest_c.sortkey == 'c'
assert p2.latest_c.sortkey == 'c'
assert p3.latest_c.sortkey == 'g'
Alternatively, you can on some platforms use LIMIT, which can produce faster results since you avoid the aggregation and can join the collection item on its primary key: 或者,您可以在某些平台上使用LIMIT,这可以产生更快的结果,因为您可以避免聚合并可以在其主键上加入集合项:
latest_c = select([child.c.id]).\
where(child.c.parent_id==parent.c.id).\
order_by(child.c.sortkey.desc()).\
limit(1).\
correlate(parent).\
as_scalar()
mapper(Parent, parent, properties={
'all_c':relation(Child),
'latest_c':relation(Child,
primaryjoin=and_(
child.c.id==latest_c,
child.c.parent_id==parent.c.id
),
uselist=False
)
})
Here is a version of @zzzeek 's answer that uses declarative rather than imperative mapping, using declared_attr to insert the relationship into the parent's __mapper_args__
.这是@zzzeek的答案的一个版本,它使用声明式而不是命令式映射,使用declared_attr将关系插入到父级的__mapper_args__
中。
import sqlalchemy as sa
from sqlalchemy import orm
Base = orm.declarative_base()
class Child(Base):
__tablename__ = 'children'
id = sa.Column(sa.Integer, primary_key=True)
sortkey = sa.Column(sa.Integer, nullable=False)
parent_id = sa.Column(sa.Integer, sa.ForeignKey('parents.id'))
parent = orm.relationship('Parent', back_populates='children')
class Parent(Base):
__tablename__ = 'parents'
id = sa.Column(sa.Integer, primary_key=True)
children = orm.relationship('Child', back_populates='parent')
@orm.declared_attr
def __mapper_args__(cls):
children = Child.__table__
most_recent_child = (
sa.select(children.c.id)
.where(children.c.parent_id == cls.id)
.order_by(children.c.sortkey.desc())
.limit(1)
.correlate(cls.__table__)
.scalar_subquery()
)
rel = orm.relation(
Child,
primaryjoin=sa.and_(
Child.id == most_recent_child, Child.parent_id == cls.id
),
uselist=False,
viewonly=True,
)
return {'properties': {'latest_child': rel}}
# Build and test.
engine = sa.create_engine('sqlite://', echo=True, future=True)
Base.metadata.create_all(engine)
Session = orm.sessionmaker(engine, future=True)
with Session.begin() as s:
children = [Child(sortkey=i) for i in range(1, 6)]
parent = Parent(children=children)
s.add(parent)
with Session() as s:
w = s.scalars(sa.select(Parent)).first()
assert w.latest_child.sortkey == 5, f'{w.latest_child.sortkey=}'
assert len(w.children) == 5, f'{len(w.children)=}'
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.