1. ホーム
  2. python

[解決済み] Djangoでカウントアノテーションのためにオブジェクトをフィルタリングする方法は?

2022-04-30 15:14:16

質問

シンプルなDjangoのモデルを考える EventParticipant :

class Event(models.Model):
    title = models.CharField(max_length=100)

class Participant(models.Model):
    event = models.ForeignKey(Event, db_index=True)
    is_paid = models.BooleanField(default=False, db_index=True)

イベントクエリに参加者総数を簡単にアノテートできます。

events = Event.objects.all().annotate(participants=models.Count('participant'))

でフィルタリングされた参加者数をアノテーションするには? is_paid=True ?

クエリを実行する必要があります すべてのイベント 参加者の数に関係なく、例えば、注釈付きの結果でフィルタリングする必要はない。もし 0 の参加者がいればそれでいい。 0 をアノテーション値で指定します。

ドキュメントからの例 でアノテーションするのではなく、クエリからオブジェクトを除外してしまうからです。 0 .

更新しました。 Django 1.8では、新たに 条件式機能 で、こんなことができるようになりました。

events = Event.objects.all().annotate(paid_participants=models.Sum(
    models.Case(
        models.When(participant__is_paid=True, then=1),
        default=0,
        output_field=models.IntegerField()
    )))

アップデート2 Django 2.0では、新たに 条件付き集計 を参照してください。 受け付けた答え の下にあります。

アップデート3. Django 3.x 用にお願いします。 以下の回答をご覧ください。 .

解決方法は?

条件付き集計 Django 2.0 では、今までの手間をさらに減らすことができます。また、これは Postgres の filter ロジックは、sum-caseよりもいくらか高速です(20-30%という数字が飛び交っているのを見たことがあります)。

とにかく、あなたの場合、次のような単純なものを見ています。

from django.db.models import Q, Count
events = Event.objects.annotate(
    paid_participants=Count('participants', filter=Q(participants__is_paid=True))
)

については、ドキュメントに別のセクションがあります。 アノテーションのフィルタリング . 条件付き集計と同じようなものですが、上の例のような感じです。いずれにせよ、これは私が以前にやっていた厄介なサブクエリよりもずっと健全なものです。