Подсчет аннотаций добавляет ненужную группу по заявке для всех полей - PullRequest
1 голос
/ 07 января 2020

Я хочу сгенерировать следующий запрос:

select id, (select count(*) from B where B.x = A.x) as c from A

Что должно быть достаточно просто с выражением Подзапрос . За исключением того, что к моему запросу на счет добавляется оператор group by, от которого я не могу избавиться:

from django.contrib.contenttypes.models import ContentType

str(ContentType.objects.annotate(c=F('id')).values('c').query)
# completely fine query with annotated field
'SELECT "django_content_type"."id" AS "c" FROM "django_content_type"'

str(ContentType.objects.annotate(c=Count('*')).values('c').query)
# gets group by for every single field out of nowhere
'SELECT COUNT(*) AS "c" FROM "django_content_type" GROUP BY "django_content_type"."id", "django_content_type"."app_label", "django_content_type"."model"'

, что делает результат [{'c': 1}, {'c': 1}, {'c': 1}, {'c': 1},...] вместо [{c:20}]. Но подзапросы должны иметь только одну строку результатов, чтобы их можно было использовать.

Поскольку запрос предполагается использовать в подзапросе, я не могу использовать .count() или .aggregate(), так как они оцениваются мгновенно и жалуются об использовании OuterRef выражения.

Пример с подзапросом:

str(ContentType.objects.annotate(fields=Subquery(
    Field.objects.filter(model_id=OuterRef('pk')).annotate(c=Count('*')).values('c')
)).query)

Генерирует

SELECT "django_content_type"."id",
       "django_content_type"."app_label",
       "django_content_type"."model",
       (SELECT COUNT(*) AS "c"
        FROM "meta_field" U0
        WHERE U0."model_id" = ("django_content_type"."id")
        GROUP BY U0."id", U0."model_id", U0."module", U0."name", U0."label", U0."widget", U0."visible", U0."readonly",
                 U0."desc", U0."type", U0."type_model_id", U0."type_meta_id", U0."is_type_meta", U0."multi",
                 U0."translatable", U0."conditions") AS "fields"
FROM "django_content_type"

Ожидаемый запрос:

SELECT "django_content_type"."id",
       "django_content_type"."app_label",
       "django_content_type"."model",
       (SELECT COUNT(*) AS "c"
        FROM "meta_field" U0
        WHERE U0."model_id" = ("django_content_type"."id")) AS "fields"
FROM "django_content_type"

Обновление: (чтобы добавить модели из реального приложения, запрошенного в комментариях):

class Translation(models.Model):
    field = models.ForeignKey(MetaField, models.CASCADE)
    ref_id = models.IntegerField()
    # ... other fields

class Choice(models.Model):
    meta = models.ForeignKey(MetaField, on_delete=models.PROTECT)
    # ... other fields

Мне нужен запрос, чтобы получить количество переводов, доступных для каждого выбора, где Translation.field_id относится к Choice.meta_id и Translation.ref_id относится на Choice.id.

Причина отсутствия внешних ключей заключается в том, что не все мета-поля являются полями выбора (например, текстовые поля также могут иметь переводы). Я мог бы создать отдельную таблицу для каждой переводимой сущности, но эта настройка должна быть простой в использовании с подзапросом count, в котором нет оператора group by.

1 Ответ

1 голос
/ 07 января 2020

ОБНОВЛЕНИЕ Вот запрос с использованием подзапроса, который должен приблизиться к тому, что вы хотите:

str(ContentType.objects.annotate(fields=Subquery(
    Field.objects.filter(model_id=OuterRef('pk')).values('model').annotate(c=Count('pk')).values('c')
)).query)

Единственное, что я сделал, это добавило предложение values('model') group_by, которое делает Count('pk') на самом деле работает, поскольку объединяет все строки в одну.

Он вернет null вместо 0, когда нет связанных строк, которые вы, вероятно, можете преобразовать в 0, используя функцию Coalesce или Case ... When ... then.

Точный запрос не представляется возможным с Django ORM, хотя вы можете достичь того же результата с

Choice.objects.annotate(c=Count(
    'meta__translation',
    distinct=True,
    filter=Q(meta__translation__ref_id=F('id'))
))

В качестве альтернативы посмотрите на django - Пакет sql -utils , также упомянутый в этом посте .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...