クエリセットを指定して、関連するオブジェクト(ModelA)の数を次のように追加します。
qs = User.objets.all()
qs.annotate(modela__count=models.Count('modela'))
しかし、基準を満たすだけのModelAを数える方法はありますか?たとえば、deleted_atがnullであるModelAをカウントしますか?
正しく機能しない2つの解決策を試しました。
1)@knbkが提案したように、注釈を付ける前にフィルターを使用します。
qs = User.objects.all().filter(modela__deleted_at__isnull=True).annotate(modela__count=models.Count('modela', distinct=True))
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"
問題はWHERE句にあります。確かに、LEFT JOINがありますが、後のWHERE条件により、プレーンJOINになりました。意図したとおりに機能させるには、条件をJOIN句にプルアップする必要があります。
だから、代わりに
LEFT OUTER JOIN "modela" ON ( "users"."id" = "modela"."user_id" )
WHERE "modela"."deleted_at" IS NULL
プレーンSQLで直接実行すると機能する次のものが必要です。
LEFT OUTER JOIN "modela" ON ( "users"."id" = "modela"."user_id" )
AND "modela"."deleted_at" IS NULL
生のクエリを実行せずにこれを取得するようにクエリセットを変更するにはどうすればよいですか?
2)他の人が示唆しているように、条件付き集計を使用できます。
私は以下を試しました:
qs = User.objects.all().annotate(modela__count=models.Count(Case(When(modela__deleted_at__isnull=True, then=1))))
これは次の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"
そうすることで、すべてのユーザーを取得します(したがって、LEFT JOINは正しく機能します)が、ModelAをまったく持っていないすべてのユーザーのmodela__count
に対して(0ではなく)「1」を取得します。カウントするものがないのに、なぜ0ではなく1を取得するのですか?どうすればそれを変えることができますか?
LEFT JOIN
では、対応する行がないため、modela
のすべてのフィールドがNULL
になる可能性があります。そう
modela.deleted_at IS NULL
...一致する行だけでなく、対応する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`
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,
)
)
)
)
通知:
@YAmikepは、Django 1.8.のバグにより、生成されたSQLにINNER JOIN
ではなくLEFT JOIN
が含まれることを発見しました。対応する外部キー関係のない行を失います。Django 1.8.2以降バージョンを使用して修正します。
注釈を付ける前に、単純にフィルタリングできます。
from Django.db.models import Q, Count
qs = ModelA.objects.filter(account__prop1__isnull=False).annotate(account_count=Count('account'))