Прежде всего, я думаю, что COUNT(DISTINCT)
с поддержкой более 1 выражения является расширением MySQL. Вы можете добиться того же самого, например, PostgreSQL со значениями ROW
, но в отношении NULL поведение не то же самое. В MySQL, если какое-либо из значений выражения оценивается как NULL, строка не квалифицируется. Это также приводит к разнице между двумя запросами в вопросе:
- Если любой из
Serial
, DatumOrig
или Glucose
равен NULL в запросе COUNT(DISTINCT)
, эта строка не квалифицируется или другими словами не учитывается. COUNT(*)
- количество подзапросов anon_1
, или, другими словами, количество строк. SELECT DISTINCT Serial, DatumOrig, Glucose
будет включать в себя (различные) строки с NULL.
Если посмотреть на вывод EXPLAIN
для 2 запросов, то выглядит, что подзапрос заставляет MySQL использовать временную таблицу. Скорее всего, это приведет к разнице в производительности, особенно если она материализована на диске.
Создание многозначного запроса COUNT(DISTINCT)
в SQLAlchemy немного сложнее, потому что count()
является обобщенным Функция c и реализована ближе к стандарту 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()
, который далек от читаемого и обслуживаемого кода, но выполняет работу сейчас , Другой вариант - написать пользовательскую конструкцию, которая реализует расширение 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()