【问题标题】:SQLAlchemy - filtering func.count within querySQLAlchemy - 在查询中过滤 func.count
【发布时间】:2018-08-09 20:17:42
【问题描述】:

假设我有一个包含一列的表,其中包含一些整数值,我想计算该列超过 200 的值的百分比。

这是关键,如果我可以在一个可以使用 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)

另外,最好不要在客户端进行百分比计算,比如这样。

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)

但我显然无法在 count statemenet 中进行过滤,也无法在查询结束时应用过滤器,因为如果我在最后应用 > 200 约束,total_count 将不起作用。

也可以选择使用 RAW SQL,不一定非要使用 Sqlalchemy

【问题讨论】:

  • 您使用的是哪个数据库?
  • MariaDB @IljaEverilä

标签: python sql sqlalchemy flask-sqlalchemy


【解决方案1】:

遗憾的是,MariaDB 不支持aggregate FILTER clause,但您可以解决using a CASE expressionNULLIF,因为COUNT 返回给定表达式的非空值的计数:

from sqlalchemy import case

...

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

考虑到这一点,您可以简单地计算百分比

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

虽然有一个优势:如果func.count(ClassA.value) is 0 会怎样?根据您是否将 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')

最后,您可以为 mysql 方言创建一个 compilation extension,将 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)

有了这个,你可以将股息表示为

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

编译成

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

【讨论】:

  • 非常感谢伊利亚!
猜你喜欢
  • 1970-01-01
  • 2015-01-15
  • 2023-03-30
  • 1970-01-01
  • 1970-01-01
  • 2021-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多