繁体   English   中英

如何定义代表集合中最新 object 的 SQLAlchemy 关系?

[英]How do I define a SQLAlchemy relation representing the latest object in a collection?

我有一个 SQLAlchemy model 与表x和表y之间的一对多关系。 yy.x_id = x.idid最大的记录(如果有)是特殊的。 Class X和 class Y map 表xy

我知道如何定义X.all_y ( ORDER BY y.id )。 如何定义X.latest_y等同于X.all_y[-1]

纯粹的关系方式要求使用子查询来获取与父级相关的“最新”或“最大”值,然后将其与集合的成员等同。 这意味着如果您在确定“最新”的列上放置索引,您将获得最佳结果:

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'

或者,您可以在某些平台上使用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
    )
})

这是@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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM