【问题标题】:SQLAlchemy - How can I make eager loading count propertySQLAlchemy - 我怎样才能使急切加载计数属性
【发布时间】:2013-12-27 13:28:48
【问题描述】:

我想为包含计数的模型创建一个属性。

由于我总是需要该属性,因此我想使用JOIN 进行查询,例如使用sqlalchemy.orm.relationshiplazy='joined'

例如,我定义了如下模型

import sqlalchemy as s, func
from sqlalchemy.orm import relatioship

# ...

class Foo(Base):
    __tablename__ = 'foo'
    id = s.Column(s.Integer, primary_key=True)
    bar_id = s.Column(s.Integer, s.ForeignKey('bar.id'))
    bar = relationship('Bar')


class Bar(Base):
    __tablename__ = 'bar'
    id = s.Column(s.Integer, primary_key=True)

    @property
    def foo_count(self):
        return Foo.query.filter_by(bar=self).count()

当我访问属性foo_count 时,它将向 DBMS 发送查询。

由于我总是访问这个属性,我想像这样急切地加载它的计数属性

# Not session.query(Bar, func.count(Foo.id)).join(Foo) ...
bar = Bar.query.first()

SQL 会是这样的

SELECT id, COUNT(Foo.id)
FROM bar 
INNER JOIN foo
    ON bar.id = foo.id

那么bar.foo_count就不会发生SQL查询了。

如何创建foo_count 之类的属性?

【问题讨论】:

    标签: python sqlalchemy


    【解决方案1】:

    我用sqlalchemy.orm.column_property解决了这个问题

    我用关注替换了foo_count

    import sqlalchemy as s, func, select
    from sqlalchemy.orm import relationship, column_property
    
    # ...
    
    class Foo(Base):
        __tablename__ = 'foo'
        id = s.Column(s.Integer, primary_key=True)
        bar_id = s.Column(s.Integer, s.ForeignKey('bar.id'))
        bar = relationship('Bar')
    
    
    class Bar(Base):
        __tablename__ = 'bar'
        id = s.Column(s.Integer, primary_key=True)
    
        foo_count = column_property(
            select([func.count(Foo.id)])
            .where(Foo.bar_id == id)
        )
    

    【讨论】:

    【解决方案2】:

    请查看Hybrid Attribute 扩展名。

    您的对象模型将如下所示:

    class Foo(Base):
        __tablename__ = 'foo'
        id = Column(Integer, primary_key=True)
        bar_id = Column(Integer, ForeignKey('bar.id'))
        bar = relationship('Bar')
    
    class Bar(Base):
        __tablename__ = 'bar'
        id = Column(Integer, primary_key=True)
    
        @hybrid_property
        def foo_count(self):
            return object_session(self).query(Foo).filter(Foo.bar==self).count()
    
        @foo_count.expression
        def foo_count(cls):
            return select([func.count(Foo.id)]).where(Foo.bar_id == cls.id).label('foo_count')
    

    foo_count 不会被急切加载,但您可以在如下查询中使用它(SELECTWHERE 子句:

    qry = session.query(Bar, Bar.foo_count).filter(Bar.foo_count > 0)
    for (bar, bar_foo_count) in qry:
        print bar, bar_foo_count
    

    如您所见,该查询将在一个查询中返回 (Bar, foo_count) 的元组,现在您可以用它做任何您想做的事情。

    【讨论】:

    • 在查询中没有多个实体的情况下是否存在加入方式?我想像关系属性一样自动执行此操作。
    • 如果您有大量 Bars 列表,这意味着您将运行相同的计数查询数百或数千次。我认为您应该编写一个直接 SQL 查询,在其中选择 Bar + count(Foo.id) 的所有列,这意味着您需要一个 JOIN 查询。
    • @OzzyTheGiant:实际情况并非如此。 Sqlalchemy 将生成这样的查询SELECT bar.*, (SELECT count(foo.id) AS count_1 FROM foo WHERE foo.bar_id = bar.id) AS foo_count FROM bar。如您所见,只有一个连接的子查询。在我回答的最后一个查询中,子选择将重复 3 次在同一个查询中,我假设 RDBMS 查询优化引擎会处理这个问题。使用column_property 的公认答案解决方案生成基本相同的结构。
    • 当 Foo 和 Bar 之间有一个中间表时会发生什么? (就像在 M2M 关系中一样)
    猜你喜欢
    • 2018-12-12
    • 2010-11-19
    • 2014-05-15
    • 1970-01-01
    • 2011-04-10
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多