SQLAlchemy:一个查询中的多个计数

Posted

技术标签:

【中文标题】SQLAlchemy:一个查询中的多个计数【英文标题】:SQLAlchemy: several counts in one query 【发布时间】:2014-09-14 23:52:01 【问题描述】:

我很难优化我的 SQLAlchemy 查询。我的 SQL 知识非常基础,我无法从 SQLAlchemy 文档中获得所需的东西。

假设以下非常基本的一对多关系:

class Parent(Base):
    __tablename__ = "parents"
    id = Column(Integer, primary_key = True)
    children = relationship("Child", backref = "parent")

class Child(Base):
    __tablename__ = "children"
    id = Column(Integer, primary_key = True)
    parent_id = Column(Integer, ForeignKey("parents.id"))
    naughty = Column(Boolean)

我怎么可能:

查询每个父级的 (Parent, count_of_naughty_children, count_of_all_children) 元组?

在花大量时间谷歌搜索之后,我发现了如何分别查询这些值:

# The following returns tuples of (Parent, count_of_all_children):
session.query(Parent, func.count(Child.id)).outerjoin(Child, Parent.children).\
    group_by(Parent.id)
# The following returns tuples of (Parent, count_of_naughty_children):
al = aliased(Children, session.query(Children).filter_by(naughty = True).\
    subquery())
session.query(Parent, func.count(al.id)).outerjoin(al, Parent.children).\
    group_by(Parent.id)

我试图以不同的方式将它们组合起来,但没有得到我想要的。

查询所有有80%以上顽皮孩子的父母?编辑:naughty 可能为 NULL。

我猜这个查询将基于前一个查询,按顽皮/全部比率过滤。

感谢任何帮助。

编辑:感谢 Antti Haapala 的帮助,我找到了第二个问题的解决方案:

avg = func.avg(func.coalesce(Child.naughty, 0)) # coalesce() treats NULLs as 0
# avg = func.avg(Child.naughty) - if you want to ignore NULLs
session.query(Parent).join(Child, Parent.children).group_by(Parent).\
    having(avg > 0.8)

如果孩子的naughty 变量,它会找到平均值,将 False 和 NULL 视为 0,将 True 视为 1。使用 mysql 后端进行测试,但也应该适用于其他人。

【问题讨论】:

【参考方案1】:

count() sql 聚合函数非常简单;它为您提供每组中非空值的总数。考虑到这一点,我们可以调整您的查询,为您提供正确的结果。

print (Query([
    Parent,
    func.count(Child.id),
    func.count(case(
        [((Child.naughty == True), Child.id)], else_=literal_column("NULL"))).label("naughty")])

    .join(Parent.children).group_by(Parent)
    )

这会产生以下 sql:

SELECT 
 parents.id AS parents_id, 
 count(children.id) AS count_1, 
 count(CASE WHEN (children.naughty = 1) 
       THEN children.id 
       ELSE NULL END) AS naughty 
FROM parents 
JOIN children ON parents.id = children.parent_id 
GROUP BY parents.id

【讨论】:

【参考方案2】:

如果您的查询只是为了让有> 80 % 孩子顽皮的父母,您可以在大多数数据库中将naughty 转换为整数,然后取平均值;那么having这个平均值大于0.8

这样你就会得到类似的东西

from sqlalchemy.sql.expression import cast

naughtyp = func.avg(cast(Child.naughty, Integer))
session.query(Parent, func.count(Child.id), naughtyp).join(Child)\
    .group_by(Parent.id).having(naughtyp > 0.8).all()

【讨论】:

非常感谢,成功了。但我没有提到在我的真实模型中“淘气”可能是空的 - 对不起,我的错。空值被 avg 忽略,所以这个解决方案不是我想要的。 func.coalesce() 帮我解决了这个问题^^

以上是关于SQLAlchemy:一个查询中的多个计数的主要内容,如果未能解决你的问题,请参考以下文章

在 SQLAlchemy 中,当有多个关系时,如何访问查询中的引用属性?

使用 SQLAlchemy 过滤 sql 计数(*)

我从 sqlalchemy 得到一个“幽灵”回滚,但在使用 psql 和 postgres 时没有

sqlalchemy 映射的小例子

sqlalchemy中的分组和计数功能

来自多个表的SQLAlchemy查询