152

シンプルな 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 には新しい条件付き集計機能があります。以下の受け入れられた回答を参照してください。

Update 3. Django 3.x については、以下の回答を確認してください。

4

6 に答える 6

95

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()
    )))
于 2015-06-10T11:07:28.447 に答える
46

アップデート

私が言及したサブクエリのアプローチは、 subquery-expressionsを介して Django 1.11 でサポートされるようになりました。

Event.objects.annotate(
    num_paid_participants=Subquery(
        Participant.objects.filter(
            is_paid=True,
            event=OuterRef('pk')
        ).values('event')
        .annotate(cnt=Count('pk'))
        .values('cnt'),
        output_field=models.IntegerField()
    )
)

集計(sum+case)よりもこれを好みます。これは、 (適切なインデックスを使用して)最適化する方が高速で簡単なためです。

古いバージョンの場合、同じことを使用して実現できます.extra

Event.objects.extra(select={'num_paid_participants': "\
    SELECT COUNT(*) \
    FROM `myapp_participant` \
    WHERE `myapp_participant`.`is_paid` = 1 AND \
            `myapp_participant`.`event_id` = `myapp_event`.`id`"
})
于 2015-06-10T09:58:27.263 に答える
6

.values代わりに、クエリセットのメソッドを使用することをお勧めしますParticipant

要するに、あなたがしたいことは次のように与えられます:

Participant.objects\
    .filter(is_paid=True)\
    .values('event')\
    .distinct()\
    .annotate(models.Count('id'))

完全な例は次のとおりです。

  1. 2 を作成しますEvent

    event1 = Event.objects.create(title='event1')
    event2 = Event.objects.create(title='event2')
    
  2. それらに を追加Participantします。

    part1l = [Participant.objects.create(event=event1, is_paid=((_%2) == 0))\
              for _ in range(10)]
    part2l = [Participant.objects.create(event=event2, is_paid=((_%2) == 0))\
              for _ in range(50)]
    
  3. フィールドごとにすべてParticipantの をグループ化します。event

    Participant.objects.values('event')
    > <QuerySet [{'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 1}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, {'event': 2}, '...(remaining elements truncated)...']>
    

    ここで個別が必要です:

    Participant.objects.values('event').distinct()
    > <QuerySet [{'event': 1}, {'event': 2}]>
    

    ここで.values.distinctが行っているのはParticipant、 element によってグループ化された の2 つのバケットを作成していることeventです。これらのバケットにはParticipant.

  4. これらのバケットにはオリジナルのセットが含まれているため、これらのバケットに注釈を付けることができますParticipant。ここでは、 の数を数えたいと思いますParticipant。これは、それらのバケット内の要素の を数えることによって単純に行われますid(それらは であるためParticipant):

    Participant.objects\
        .values('event')\
        .distinct()\
        .annotate(models.Count('id'))
    > <QuerySet [{'event': 1, 'id__count': 10}, {'event': 2, 'id__count': 50}]>
    
  5. 最後にParticipantis_paidbeingのみがTrue必要な場合は、前の式の前にフィルターを追加するだけで、上記の式が得られます。

    Participant.objects\
        .filter(is_paid=True)\
        .values('event')\
        .distinct()\
        .annotate(models.Count('id'))
    > <QuerySet [{'event': 1, 'id__count': 5}, {'event': 2, 'id__count': 25}]>
    

唯一の欠点は、上記のメソッドからEventしか取得できないため、後で取得する必要があることです。id

于 2017-11-26T20:32:00.440 に答える