django Count注解为所有字段添加了不需要的group by语句

g9icjywg  于 2023-01-31  发布在  Go
关注(0)|答案(2)|浏览(194)

我想生成以下查询:

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_idTranslation.ref_id指的是Choice.id
没有外键的原因是不是所有的 meta字段都是选择字段(例如,文本字段也可能有翻译)。我可以为每个可翻译的实体创建一个单独的表,但这种设置应该很容易与没有group by语句的count子查询一起使用。

o7jaxewo

o7jaxewo1#

UPDATE下面是一个使用子查询的查询,它应该接近您所需的内容:

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,您可以使用Coalesce函数或Case ... When ... then将其转换为0。
使用DjangoORM不可能实现您想要的精确查询,尽管可以使用

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

或者看看django-sql-utils包,this post中也提到了这一点。

rhfm7lfc

rhfm7lfc2#

这是一个有点肮脏的黑客,但在深入研究Django的ORM代码后,我发现以下代码对我来说非常有效(使用您自己的子查询):

counting_subquery = Subquery( Field.objects
                                       .filter( model_id = OuterRef( 'pk' ) )
                                       .annotate( c = Count( '*' ) )
                                       .values('c') )

    # Note: the next line fixes a bug in the Django ORM, where the subquery defined above
    # triggers an unwanted group_by clause in the generated SQL which ruins the count operation.
    counting_subquery.query.group_by = True

    results = ContentType.objects
                         .annotate( fields_count = Subquery( counting_subquery ) )
                         ...

关键是将group_by设置为True,这样就可以去掉SQL中不需要的group_by子句。
我对此并不满意,因为它依赖于Django未记录的行为来工作。但我可以接受它,我更不满意在子查询中使用直接SQL的可维护性...

相关问题