繁体   English   中英

SQLAlchemy:我可以在结果中添加一个聚合作为“虚拟列”吗?

[英]SQLAlchemy: can I add an aggregate as “virtual column” in a result?

我有一个通常看起来像的查询

def get_models_with_children(ids):
   query = MyModel.query.filter(MyModel.id.in_(ids))
           .join(Child, Child.parent_id = Child.id)
           .groupBy(MyModel.id)
           .having(func.count(Child.id) > 0)

   return query.all()

有时,我也想实际检索计数。 我可以很容易地做到这一点:

def get_models_with_children(ids, return_count):
   query = MyModel.query

   if return_count:
       query = query.add_columns(func.count(Child.id).label("child_count"))

   query = query.filter(MyModel.id.in_(ids))
           .join(Child, Child.parent_id = Child.id)
           .groupBy(MyModel.id)
           .having(func.count(Child.id) > 0)

   return query.all()

这工作正常,但现在,而不是List[MyModel]回来了,我用MyModelchild_count键得到了不同形状的结果。 如果我想要 MyModel 的 id,如果我没有添加计数,我会执行result[0].id如果我添加了,我会执行result[0].MyModel.id

有没有什么办法可以展平的结果,使东西的恢复看起来像一个MyModel一个额外的child_count列?

def do_stuff_with_models():
    result = get_models_with_children([1, 2, 3], True)
    for r in result:
         # can't do this, but I want to:
         print(r.id)
         print(r.child_count)

         # instead I have to do this:
         print(r.MyModel.id)
         print(r.child_count)

sqlalchemy.util.KeyedTuple具有MyModelchild_count不同形状结果的类型*

Query返回的包含多个 ORM 实体和/或列表达式的结果行使用此类返回行。

您可以通过显式指定查询的列来有效地展平它们。 下面是一个完整的示例(在SQLAlchemy==1.3.12上测试)。

普通表列属性

楷模:

import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base


Base = declarative_base()

class User(Base):

    __tablename__ = 'user'

    user_id = sa.Column(sa.Integer, sa.Sequence('user_id_seq'), primary_key=True)
    username = sa.Column(sa.String(80), unique=True, nullable=False)

    def __repr__(self):
        return f'User({self.user_id!r}, {self.username!r})'

class Token(Base):

    __tablename__ = 'token'

    token_id = sa.Column(sa.Integer, sa.Sequence('token_id_seq'), primary_key=True)
    user_id = sa.Column(sa.Integer, sa.ForeignKey('user.user_id'), nullable=False)
    user = sa.orm.relationship('User')
    value = sa.Column(sa.String(120), nullable=False)

    def __repr__(self):
        return f'Token({self.user.username!r}, {self.value!r})'

连接并填充一些数据:

engine = sa.create_engine('sqlite://')
Base.metadata.create_all(engine)
Session = sa.orm.sessionmaker(bind=engine)
session = Session()

user1 = User(username='joe')
user2 = User(username='john')
token1 = Token(user=user1, value='q1w2e3r4t56')

session.add_all([user1, user2, token1])
session.commit()

现在,让我们将“虚拟”列定义为用户是否拥有令牌:

query = session.query(User)
exists = (
    sa.exists()
    .where(User.user_id == Token.user_id)
    .correlate(User)
    .label("has_token")
)
query = query.add_columns(exists)
query.all()  # [(User(1, 'joe'), True), (User(2, 'john'), False)]

这是不想要的形状。 这是展平它的方法:

query = session.query(*[getattr(User, n) for n in User.__table__.columns.keys()])
query = query.add_columns(exists)
query.all()  # [(1, 'joe', True), (2, 'john', False)]

假设您知道模型,则可以为现有查询定义列:

query = session.query(User)
# later down the line
query = query.with_entities(*[
    getattr(User, n) for n in User.__table__.columns.keys()])
query = query.add_columns(exists)
query.all()  # [(1, 'joe', True), (2, 'john', False)]

柱束

使用sqlalchemy.orm.Bundle并将single_entity传递给它也可以实现相同的目的。

bundle = sa.orm.Bundle(
    'UserBundle', User.user_id, User.username, exists, single_entity=True)
query = session.query(bundle)
query.all()  # [(1, 'joe', True), (2, 'john', False)]

关系属性问题

对于复杂的模型,它变得复杂。 可以使用sqlalchemy.orm.mapper.Mapper.attrs检查模型(映射类)属性并采用class_attribute

# replace
[getattr(User, n) for n in User.__table__.columns.keys()]
# with
[mp.class_attribute for mp in sa.inspect(User).attrs]

但在这种情况下, relationship属性在没有ON子句的查询的FROM子句中变成了它们的目标表,有效地产生了笛卡尔积。 并且“连接”必须手动定义,因此这不是一个好的解决方案。 请参阅此答案SQLAlchemy 用户组讨论

查询表达式属性

我自己最终使用了查询表达式,因为现有代码中存在关系问题。 可以通过对模型进行最少的修改,将查询时 SQL 表达式作为映射属性来逃脱。

User.has_tokens = sa.orm.query_expression()
...
query = query.options(sa.orm.with_expression(User.has_tokens, exists))
query.all()  # [User(1, 'joe'), User(2, 'john')]
[u.has_tokens for u in query.all()]  # [True, False]

*其实它是产生于即时sqlalchemy.util._collections.result与MRO sqlalchemy.util._collections.resultsqlalchemy.util._collections._LWclass sqlalchemy.util._collections.AbstractKeyedTupletupleobject ,但是这细节。 此答案中提供了有关如何使用lightweight_named_tuple组创建类的更多详细信息。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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