简体   繁体   English

SQLAlchemy-在查询中过滤func.count

[英]SQLAlchemy - filtering func.count within query

Let's say that I have a table with a column, that has some integer values and I want to calculate the percentage of values that are over 200 for that column. 假设我有一个带有列的表,该表具有一些整数值,并且我想计算该列的值超过200的百分比。

Here's the kicker, I would prefer if I could do it inside one query that I could use group_by on. 这是一个关键,如果我可以在可以使用group_by的一个查询中执行此操作,则希望这样做。

results = db.session.query(
        ClassA.some_variable,
        label('entries', func.count(ClassA.some_variable)),
        label('percent', *no clue*)
  ).filter(ClassA.value.isnot(None)).group_by(ClassA.some_variable)

Alternately it would be okay thought not prefered to do the percentage calculation on the client side, something like this. 或者,也可以考虑不喜欢在客户端进行百分比计算,就像这样。

results = db.session.query(
        ClassA.some_variable,
        label('entries', func.count(ClassA.some_variable)),
        label('total_count', func.count(ClassA.value)),
        label('over_200_count', func.count(ClassA.value > 200)),
  ).filter(ClassA.value.isnot(None)).group_by(ClassA.some_variable)

But I obviously can't filter within the count statemenet, and I can't apply the filter at the end of the query, since if I apply the > 200 constraint at the end, total_count wouldn't work. 但是我显然不能在count statemenet内进行过滤,也无法在查询的末尾应用过滤器,因为如果在末尾应用> 200约束,total_count将无法工作。

Using RAW SQL is an option too, it doesn't have to be Sqlalchemy 也可以使用RAW SQL,它不一定是Sqlalchemy

MariaDB unfortunately does not support the aggregate FILTER clause , but you can work around that using a CASE expression or NULLIF , since COUNT returns the count of non-null values of given expression: 不幸的是,MariaDB不支持聚合FILTER子句 ,但是您可以使用CASE表达式NULLIF解决该问题,因为COUNT返回给定表达式的非空值的计数:

from sqlalchemy import case

...

func.count(case([(ClassA.value > 200, 1)])).label('over_200_count')

With that in mind you can calculate the percentage simply as 考虑到这一点,您可以简单地计算百分比

(func.count(case([(ClassA.value > 200, 1)])) * 1.0 /
 func.count(ClassA.value)).label('percent')

though there's that one edge: what if func.count(ClassA.value) is 0 ? 尽管有一个优势:如果func.count(ClassA.value) 0怎么办? Depending on whether you'd consider 0 or NULL a valid return value you could either use yet another CASE expression or NULLIF: 根据您是将0还是NULL视为有效的返回值,您可以使用另一个CASE表达式或NULLIF:

dividend = func.count(case([(ClassA.value > 200, 1)])) * 1.0
divisor = func.count(ClassA.value)

# Zero
case([(divisor == 0, 0)],
     else_=dividend / divisor).label('percent')

# NULL
(dividend / func.nullif(divisor, 0)).label('percent')

Finally, you could create a compilation extension for mysql dialect that rewrites a FILTER clause to a suitable CASE expression: 最后,您可以为mysql方言创建一个编译扩展 ,该扩展将FILTER子句重写为合适的CASE表达式:

from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import FunctionFilter
from sqlalchemy.sql.functions import Function
from sqlalchemy import case


@compiles(FunctionFilter, 'mysql')
def compile_functionfilter_mysql(element, compiler, **kwgs):
    # Support unary functions only
    arg0, = element.func.clauses

    new_func = Function(
        element.func.name,
        case([(element.criterion, arg0)]),
        packagenames=element.func.packagenames,
        type_=element.func.type,
        bind=element.func._bind)

    return new_func._compiler_dispatch(compiler, **kwgs)

With that in place you could express the dividend as 有了这一点,您可以将股息表示为

dividend = func.count(1).filter(ClassA.value > 200) * 1.0

which compiles to 编译成

In [28]: print(dividend.compile(dialect=mysql.dialect()))
count(CASE WHEN (class_a.value > %s) THEN %s END) * %s

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

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