2017-01-27 15 views
2

1日あたりのすべてのイベントの期間を合計したいと思います。これは私のモデルである:集計された集計注釈

class Event(models.Model): 
    start = models.DateTimeField() 
    end = models.DateTimeField() 

サンプルデータ:

import datetime 
from random import randint 

for i in range(0, 1000): 
    start = datetime.datetime(
     year=2016, 
     month=1, 
     day=randint(1, 10), 
     hour=randint(0, 23), 
     minute=randint(0, 59), 
     second=randint(0, 59) 
    ) 
    end = start + datetime.timedelta(seconds=randint(30, 1000)) 
    Event.objects.create(start=start, end=end) 

私はそうのように一日あたりのイベント数を取得することができます (私はextraが悪いですけど、私は、現時点では1.9を使用しています。私はTruncDateを使用して移動しますアップグレード)

Event.objects.extra({'date': 'date(start)'}).order_by('date').values('date').annotate(count=Count('id')) 

[{'count': 131, 'date': datetime.date(2016, 1, 1)}, 
{'count': 95, 'date': datetime.date(2016, 1, 2)}, 
{'count': 99, 'date': datetime.date(2016, 1, 3)}, 
{'count': 85, 'date': datetime.date(2016, 1, 4)}, 
{'count': 87, 'date': datetime.date(2016, 1, 5)}, 
{'count': 94, 'date': datetime.date(2016, 1, 6)}, 
{'count': 97, 'date': datetime.date(2016, 1, 7)}, 
{'count': 111, 'date': datetime.date(2016, 1, 8)}, 
{'count': 97, 'date': datetime.date(2016, 1, 9)}, 
{'count': 104, 'date': datetime.date(2016, 1, 10)}] 

私は期間を追加するために注釈を付けることができます。

In [3]: Event.objects.annotate(duration=F('end') - F('start')).first().duration 
Out[3]: datetime.timedelta(0, 470) 

しかし、私はイベントを数えるのと同じ方法でこの注釈を合計する方法を理解できません。私は以下を試しましたが、 'duration'にはKeyErrorがあります。

Event.objects.annotate(duration=F('end') - F('start')).extra({'date': 'date(start)'}).order_by('date').values('date').annotate(total_duration=Sum('duration')) 

そして私は日付によって、その後values句にもはやグループをdurationを追加した場合。

これは単一のクエリで可能ですが、期間フィールドをモデルに追加しないでください。

+0

そのクエリセットで 'values( 'date')'を実行すると、 'duration'フィールドがなくなり、合計するものはありません。 'values()'呼び出しに 'duration'を追加した後に 'order_by( 'date')'を追加するとどうなりますか?あるいは、 'values()'を使う必要がありますか? – ChidG

+1

ちょっと@ChidG! (Ze'evはどうですか?) 'values'はgroup_byに使われています - 私はそれが必要だと思います。 'duration'を' values'呼び出しに追加すると、 'date'だけでなく、' date'と 'duration'の両方でイベントをグループ化しようとします。 –

答えて

2

私はDjango ORMがこれをサポートしていないという回答を書こうとしていました。そして、はい、私はこの問題についてもう1時間を過ごしました(この答えを書く前にすでに1.5時間を費やしていました)が、Djangoはそれをサポートしています。ハッキングせずに。良いニュース!

import datetime as dt 

from django.db import models 
from django.db.models import F, Sum, When, Case 
from django.db.models.functions import TruncDate 

from app.models import Event 

a = Event.objects.annotate(date=TruncDate('start')).values('date').annotate(
    day_duration=Sum(Case(
     When(date=TruncDate(F('start')), then=F('end') - F('start')), 
     default=dt.timedelta(), output_field=models.DurationField() 
    )) 
) 

と、(うまくいけば)このようなものは、実際にあなたが尋ねないことを証明するためにいくつかの予備試験。

In [71]: a = Event.objects.annotate(date=TruncDate('start')).values('date').annotate(day_duration=Sum(Case(
    ...:   When(date=TruncDate(F('start')), then=F('end') - F('start')), 
    ...:   default=dt.timedelta(), output_field=models.DurationField() 
    ...: )) 
    ...:) 

In [72]: for e in a: 
    ...:  print(e) 
    ...:  
{'day_duration': datetime.timedelta(0, 41681), 'date': datetime.date(2016, 1, 10)} 
{'day_duration': datetime.timedelta(0, 46881), 'date': datetime.date(2016, 1, 3)} 
{'day_duration': datetime.timedelta(0, 48650), 'date': datetime.date(2016, 1, 1)} 
{'day_duration': datetime.timedelta(0, 52689), 'date': datetime.date(2016, 1, 8)} 
{'day_duration': datetime.timedelta(0, 45788), 'date': datetime.date(2016, 1, 5)} 
{'day_duration': datetime.timedelta(0, 49418), 'date': datetime.date(2016, 1, 7)} 
{'day_duration': datetime.timedelta(0, 45984), 'date': datetime.date(2016, 1, 9)} 
{'day_duration': datetime.timedelta(0, 51841), 'date': datetime.date(2016, 1, 2)} 
{'day_duration': datetime.timedelta(0, 63770), 'date': datetime.date(2016, 1, 4)} 
{'day_duration': datetime.timedelta(0, 57205), 'date': datetime.date(2016, 1, 6)} 

In [73]: q = dt.timedelta() 

In [74]: o = Event.objects.filter(start__date=dt.date(2016, 1, 7)) 

In [75]: p = Event.objects.filter(start__date=dt.date(2016, 1, 10)) 

In [76]: for e in o: 
    ...:  q += (e.end - e.start) 

In [77]: q 
Out[77]: datetime.timedelta(0, 49418) # Matches 2016.1.7, yay! 

In [78]: q = dt.timedelta() 

In [79]: for e in p: 
    ...:  q += (e.end - e.start) 

In [80]: q 
Out[80]: datetime.timedelta(0, 41681) # Matches 2016.1.10, yay! 

NB!これはバージョン1.9から動作しますが、TruncDate機能がないため、以前のバージョンでこれを行うことはできません。 1.8の前にはもちろん、CaseWhenのものはありません。

+0

これは非常にクールで、うまくいっています! (メカトロニクスのバックグラウンドを持った別のジャンゴナウトを見るのもいいです) –