単純なDjangoモデルEvent
およびParticipant
を検討してください。
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
で注釈するのではなくクエリから除外するため、ここでは機能しません。
Update. 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には新しい 条件付き集計 機能があります。下記の 受け入れられた回答 を参照してください。
条件付き集計 in Django 2.0を使用すると、これまでにあった無駄の量をさらに減らすことができます。これには、Postgresのfilter
ロジックも使用します。これは、合計の場合よりもやや高速です(20〜30%ほどの数字が見られます)。
とにかく、あなたの場合、私たちは次のような単純なものを見ています:
from Django.db.models import Q, Count
events = Event.objects.annotate(
paid_participants=Count('participants', filter=Q(participants__is_paid=True))
)
ドキュメントには、 アノテーションのフィルタリング についての別のセクションがあります。これは条件付き集計と同じものですが、上記の例に似ています。いずれにせよ、これは私が以前行っていた厄介なサブクエリよりもずっと健康的です。
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()
)))
UPDATE
言及したサブクエリアプローチは、 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`"
})
代わりにParticipant
クエリセットの .values
メソッドを使用することをお勧めします。
簡単に言えば、あなたがしたいことは以下によって与えられます:
Participant.objects\
.filter(is_paid=True)\
.values('event')\
.distinct()\
.annotate(models.Count('id'))
完全な例は次のとおりです。
2つのEvent
sを作成します。
event1 = Event.objects.create(title='event1')
event2 = Event.objects.create(title='event2')
それらにParticipant
sを追加します。
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)]
すべての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
でグループ化されたevent
sの2つのバケットを作成していることです。これらのバケットにはParticipant
が含まれていることに注意してください。
元のParticipant
のセットが含まれているため、これらのバケットに注釈を付けることができます。ここで、Participant
の数をカウントします。これは、これらのバケット内の要素のid
sをカウントすることで簡単に実行できます(これらはParticipant
であるため)。
Participant.objects\
.values('event')\
.distinct()\
.annotate(models.Count('id'))
> <QuerySet [{'event': 1, 'id__count': 10}, {'event': 2, 'id__count': 50}]>
最後に、Participant
のみでTrue
であるis_paid
のみが必要な場合は、前の式の前にフィルターを追加するだけで、上記の式が得られます。
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
を取得する必要があることです。
私が探している結果:
一般的に、2つの異なるクエリを使用する必要があります:
Task.objects.filter(billable_efforts__gt=0)
Task.objects.all()
しかし、私は両方を1つのクエリに含める必要があります。したがって:
Task.objects.values('report__title').annotate(withMoreThanZero=Count('assignee', distinct=True, filter=Q(billable_efforts__gt=0))).annotate(totalUniqueAssignee=Count('assignee', distinct=True))
結果:
<QuerySet [{'report__title': 'TestReport', 'withMoreThanZero': 37, 'totalUniqueAssignee': 50}, {'report__title': 'Utilization_Report_April_2019', 'withMoreThanZero': 37, 'totalUniqueAssignee': 50}]>