django annotate and count:如何过滤要包括在count中的那些

Mic*_*ael 17 django django-models django-queryset

给定一个查询集,我用以下内容添加相关对象(ModelA)的计数:

qs = User.objets.all()
qs.annotate(modela__count=models.Count('modela'))
Run Code Online (Sandbox Code Playgroud)

但是,有没有办法计算只满足标准的ModelA?例如,计算ModelA,其中deleted_at为null?

我尝试了两种不能正常工作的解决方案.

1)正如@knbk建议的那样,在注释之前使用过滤器.

qs = User.objects.all().filter(modela__deleted_at__isnull=True).annotate(modela__count=models.Count('modela', distinct=True))
Run Code Online (Sandbox Code Playgroud)

这是django生成的查询的简化版本:

SELECT COUNT(DISTINCT "modela"."id") AS "modela__count", "users".*
FROM "users"
LEFT OUTER JOIN "modela" ON ( "users"."id" = "modela"."user_id" ) 
WHERE "modela"."deleted_at" IS NULL 
GROUP BY "users"."id"
Run Code Online (Sandbox Code Playgroud)

问题来自WHERE子句.实际上,有一个LEFT JOIN,但后来的WHERE条件迫使它成为一个简单的JOIN.我需要将条件提升到JOIN子句中,以使其按预期工作.

所以,而不是

LEFT OUTER JOIN "modela" ON ( "users"."id" = "modela"."user_id" ) 
WHERE "modela"."deleted_at" IS NULL
Run Code Online (Sandbox Code Playgroud)

当我在纯SQL中直接执行它时,我需要以下内容.

LEFT OUTER JOIN "modela" ON ( "users"."id" = "modela"."user_id" ) 
AND "modela"."deleted_at" IS NULL
Run Code Online (Sandbox Code Playgroud)

如何在不执行原始查询的情况下更改查询集以获取此值?

2)正如其他人所说,我可以使用条件聚合.

我尝试了以下方法:

qs = User.objects.all().annotate(modela__count=models.Count(Case(When(modela__deleted_at__isnull=True, then=1))))
Run Code Online (Sandbox Code Playgroud)

变成以下SQL查询:

SELECT COUNT(CASE WHEN "modela"."deleted_at" IS NULL THEN 1 ELSE NULL END) AS "modela__count", "users".*
FROM "users" LEFT OUTER JOIN "modela" ON ( "users"."id" = "modela"."user_id" )
GROUP BY "users"."id"
Run Code Online (Sandbox Code Playgroud)

通过这样做,我获得了所有用户(因此LEFT JOIN正常工作),但modela__count对于没有任何ModelA的所有用户,我得到"1"(而不是0).如果没有什么可计算的,为什么我得到1而不是0?怎么能改变?

Roc*_*ite 37

在a中LEFT JOIN,每个字段都modela可能NULL因为没有相应的行.所以

modela.deleted_at IS NULL
Run Code Online (Sandbox Code Playgroud)

...不仅适用于匹配的行,而且对于那些users没有相应modela行的行也是如此.

我认为正确的SQL应该是:

SELECT COUNT(
    CASE
      WHEN
        `modela`.`user_id` IS NOT NULL  -- Make sure modela rows exist
        AND `modela`.`deleted_at` IS NULL
        THEN 1
      ELSE NULL
    END
  ) AS `modela__count`,
  `users`.*
FROM `users`
LEFT OUTER JOIN `modela`
  ON ( `users`.`id` = `modela`.`user_id` )
GROUP BY `users`.`id`
Run Code Online (Sandbox Code Playgroud)

在Django 1.8中,这应该是:

from django.db import models
qs = User.objects.all().annotate(
    modela_count=models.Count(
        models.Case(
            models.When(
                modela__user_id__isnull=False,
                modela__deleted_at__isnull=True,
                then=1,
            )
        )
    )
)
Run Code Online (Sandbox Code Playgroud)

通知:

@YAmikep发现Django 1.8.0中的一个错误使得生成的SQL有一个INNER JOIN而不是一个LEFT JOIN,所以你会丢失没有相应外键关系的行.使用Django 1.8.2或更高版本来解决这个问题.