SQLAlchemy - 如何计算多列上的不同值

Mat*_*Som 5 python mysql sql orm sqlalchemy

我有这样的疑问:

SELECT COUNT(DISTINCT Serial, DatumOrig, Glucose) FROM values;
Run Code Online (Sandbox Code Playgroud)

我尝试用SQLAlchemy这种方式重新创建它:

session.query(Value.Serial, Value.DatumOrig, Value.Glucose).distinct().count()
Run Code Online (Sandbox Code Playgroud)

但这转化为:

SELECT count(*) AS count_1
    FROM (SELECT DISTINCT 
           values.`Serial` AS `values_Serial`, 
           values.`DatumOrig` AS `values_DatumOrig`,
           values.`Glucose` AS `values_Glucose`
          FROM values)
    AS anon_1
Run Code Online (Sandbox Code Playgroud)

它不会内联调用 count 函数,而是将select unique包装到子查询中。

我的问题是: SQLAlchemy 有哪些不同的方法来计算多列上的不同选择以及它们会转换成什么?

有什么解决方案可以转化为我原来的查询吗?性能或内存使用情况有什么严重差异吗?

Ilj*_*ilä 6

首先,我认为COUNT(DISTINCT)支持多个表达式是 MySQL 的一个扩展。例如,您可以在 PostgreSQL 中使用ROW值实现相同的目的,但对于 NULL 的行为不同。在 MySQL 中,如果任何值表达式的计算结果为 NULL,则该行不合格。这也导致了问题中两个查询之间的不同:

  1. Serial如果查询中、DatumOrig、 或中的任何一个Glucose为 NULL COUNT(DISTINCT),则该行不符合条件,或者换句话说,不计数。
  2. COUNT(*)是子查询的基数anon_1,或者换句话说是行数。SELECT DISTINCT Serial, DatumOrig, Glucose将包含(不同的)带有 NULL 的行。

查看EXPLAIN2 个查询的输出,看起来子查询导致 MySQL 使用临时表。这可能会导致性能差异,尤其是在磁盘上实现时。

在 SQLAlchemy 中生成多值COUNT(DISTINCT)查询有点棘手,因为count()它是一个通用函数并且实现更接近 SQL 标准。它只接受单个表达式作为其(可选)位置参数,这同样适用于distinct(). 如果所有其他方法都失败了,您可以随时恢复到text()片段,就像在这种情况下一样:

# NOTE: text() fragments are included in the query as is, so if the text originates
# from an untrusted source, the query cannot be trusted.
session.query(func.count(distinct(text("`Serial`, `DatumOrig`, `Glucose`")))).\
    select_from(Value).\
    scalar()
Run Code Online (Sandbox Code Playgroud)

这远不是可读和可维护的代码,但现在已经完成了工作。另一种选择是编写一个实现 MySQL 扩展的自定义构造,或者按照您的尝试重写查询。形成生成所需 SQL 的自定义构造的一种方法是:

from itertools import count
from sqlalchemy import func, distinct as _distinct

def _comma_list(exprs):
    # NOTE: Magic number alert, the precedence value must be large enough to avoid
    # producing parentheses around the "comma list" when passed to distinct()
    ps = count(10 + len(exprs), -1)
    exprs = iter(exprs)
    cl = next(exprs)
    for p, e in zip(ps, exprs):
        cl = cl.op(',', precedence=p)(e)

    return cl

def distinct(*exprs):
    return _distinct(_comma_list(exprs))

session.query(func.count(distinct(
    Value.Serial, Value.DatumOrig, Value.Glucose))).scalar()
Run Code Online (Sandbox Code Playgroud)