我有一个通常看起来像
的查询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]
和MyModel
键获得了不同形状的结果,而不是child_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)
答案 0 :(得分:0)
sqlalchemy.util.KeyedTuple
是具有形状的结果的 * 类型,该结果使用MyModel
和child_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
属性在没有FROM
子句的查询的ON
子句中变成了它们的目标表,从而有效地产生了笛卡尔积。而且“连接”必须手动定义,因此这不是一个好的解决方案。参见this answer和a SQLAlchemy user group discussion。
我本人最终使用查询表达式,因为现有代码中的关系存在问题。只需query-time SQL expressions as mapped attributes,就可以对模型进行最少的修改。
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.result
,sqlalchemy.util._collections._LW
,class sqlalchemy.util._collections.AbstractKeyedTuple
,tuple
,{{1 }},但这很详细。 this answer中提供了有关如何使用object
创建类的更多详细信息。