Django : 날짜 별 그룹화 (일, 월, 연도)


94

다음과 같은 간단한 모델이 있습니다.

class Order(models.Model):
    created = model.DateTimeField(auto_now_add=True)
    total = models.IntegerField() # monetary value

그리고 다음과 같은 월별 분석을 출력하고 싶습니다.

  • 한 달 동안 판매 된 횟수 ( COUNT)
  • 결합 된 값 ( SUM)

이것을 공격하는 가장 좋은 방법이 무엇인지 모르겠습니다. 상당히 무섭게 보이는 추가 선택 쿼리를 보았지만 내 단순한 마음은 임의의 시작 연도 / 월에서 시작하여 현재 달에 도달 할 때까지 숫자를 반복하는 것이 더 나을 수 있다고 말합니다. 해당 월에 대한 쿼리 필터링. 더 많은 데이터베이스 작업-개발자 스트레스 감소!

당신에게 가장 의미있는 것은 무엇입니까? 빠른 데이터 테이블을 다시 가져올 수있는 좋은 방법이 있습니까? 아니면 내 더러운 방법이 아마도 최선의 생각일까요?

Django 1.3을 사용하고 있습니다. GROUP_BY최근에 더 좋은 방법을 추가했는지 확실하지 않습니다 .


답변:


225

Django 1.10 이상

장고 문서 목록 extra등이 빨리되지 . (@seddonym, @ Lucas03을 지적 해 주셔서 감사합니다). 나는 티켓을 열었고 이것은 jarshwah가 제공 한 솔루션입니다.

from django.db.models.functions import TruncMonth
from django.db.models import Count

Sales.objects
    .annotate(month=TruncMonth('timestamp'))  # Truncate to month and add to select list
    .values('month')                          # Group By month
    .annotate(c=Count('id'))                  # Select the count of the grouping
    .values('month', 'c')                     # (might be redundant, haven't tested) select month and count 

이전 버전

from django.db import connection
from django.db.models import Sum, Count

truncate_date = connection.ops.date_trunc_sql('month', 'created')
qs = Order.objects.extra({'month':truncate_date})
report = qs.values('month').annotate(Sum('total'), Count('pk')).order_by('month')

편집

  • 추가 된 수
  • 장고> = 1.10에 대한 정보 추가

1
어떤 데이터베이스 백엔드를 사용하고 >>> qs.extra({'month':td}).values('month').annotate(Sum('total')) [{'total__sum': Decimal('1234.56'), 'month': datetime.datetime(2011, 12, 1, 0, 0)}]
있습니까-postgres

1
@seddonym 고정 (jarshwah에게 감사드립니다)
tback

1
Truncmonth는 Django 1.8에서 사용할 수 없습니다
Sudhakaran Packianathan

2
감사합니다. 한 조인 경우 / 같은 필드가있을 수 있습니다 다른 모델에 대한 필터 (예 : 타임 스탬프) 이전 1.10 버전 코너의 경우, 다음 하나는 완전히 분야를 한정해야합니다 -'{}.timestamp'.format(model._meta.db_table)
zsepi

1
Django USE_TZ설정이 True이면 두 버전이 정확히 동일하지 않습니다. 사용하는 버전 TruncMonthTIME_ZONE자르기 전에 설정에 지정된 시간대로 타임 스탬프를 변환하고 사용하는 버전 date_trunc_sql은 데이터베이스에서 원시 UTC 타임 스탬프를 자릅니다.
Daniel Harding

36

@tback 답변에 약간의 추가 사항 : Django 1.10.6 및 postgres에서는 작동하지 않았습니다. 마지막에 order_by ()를 추가하여 수정했습니다.

from django.db.models.functions import TruncMonth
Sales.objects
    .annotate(month=TruncMonth('timestamp'))  # Truncate to month and add to select list
    .values('month')                          # Group By month
    .annotate(c=Count('id'))                  # Select the count of the grouping
    .order_by()

1
yup : docs.djangoproject.com/en/1.11/topics/db/aggregation/… ... 좋은 디자인처럼 느껴지지는 않지만 django 사람들은 매우 똑똑하므로 실제로 그렇습니다.
윌리엄스

TruncDate날짜별로 그룹화 할 수 있습니다 (일)
Neil

11

또 다른 접근 방식은 ExtractMonth. 하나의 datetime year 값만 반환되어 TruncMonth를 사용하는 데 문제가 발생했습니다. 예를 들어, 2009 년의 달만 반환되었습니다. ExtractMonth는이 문제를 완벽하게 해결했으며 다음과 같이 사용할 수 있습니다.

from django.db.models.functions import ExtractMonth
Sales.objects
    .annotate(month=ExtractMonth('timestamp')) 
    .values('month')                          
    .annotate(count=Count('id'))                  
    .values('month', 'count')  

2
    metrics = {
        'sales_sum': Sum('total'),
    }
    queryset = Order.objects.values('created__month')
                               .annotate(**metrics)
                               .order_by('created__month')

다음 queryset은 판매 합계를 결합한 한 달에 한 줄의 주문 목록입니다.sales_sum

@ 장고 2.1.7


1

여기 내 더러운 방법이 있습니다. 더럽습니다.

import datetime, decimal
from django.db.models import Count, Sum
from account.models import Order
d = []

# arbitrary starting dates
year = 2011
month = 12

cyear = datetime.date.today().year
cmonth = datetime.date.today().month

while year <= cyear:
    while (year < cyear and month <= 12) or (year == cyear and month <= cmonth):
        sales = Order.objects.filter(created__year=year, created__month=month).aggregate(Count('total'), Sum('total'))
        d.append({
            'year': year,
            'month': month,
            'sales': sales['total__count'] or 0,
            'value': decimal.Decimal(sales['total__sum'] or 0),
        })
        month += 1
    month = 1
    year += 1

년 / 월을 반복하는 더 좋은 방법이있을 수 있지만 그게 제가 관심을 갖는 것은 아닙니다. :)


BTW 잘 작동하지만 몇 달에 걸친 루프도 좋은 생각이 아닙니다. 누군가가 한 달의 날에 만들고 싶다면이 루프가 30-31 일 반복됩니다. 그렇지 않으면 그 작업을 잘
Mayank 프라 탑 싱

수백만 개의 레코드가있는 경우 너무 느립니다
jifferent

@jifferent 물론입니다! 질문을 게시 할 때 내 솔루션이 무엇인지 보여주기 위해 추가했습니다. 다른 답변이 훨씬 낫습니다.
Oli

0

다음은 임의의 기간별로 데이터를 그룹화하는 방법입니다.

from django.db.models import F, Sum
from django.db.models.functions import Extract, Cast
period_length = 60*15 # 15 minutes

# Annotate each order with a "period"
qs = Order.objects.annotate(
    timestamp=Cast(Extract('date', 'epoch'), models.IntegerField()),
    period=(F('timestamp') / period_length) * period_length,
)

# Group orders by period & calculate sum of totals for each period
qs.values('period').annotate(total=Sum(field))

0

내 데이터베이스에 주문 테이블이 있습니다. 지난 3 개월 동안 매월 주문을 계산할 것입니다.

from itertools import groupby
from dateutil.relativedelta import relativedelta

date_range = datetime.now()-relativedelta(months=3)
aggs =Orders.objects.filter(created_at=date_range)\
            .extra({'date_created':"date(created_at)"}).values('date_created')

for key , group in groupby(aggs):
     print(key,len(list(group)))

created_at는 datetime 필드입니다. 추가 기능으로 수행 한 것은 datetime 값에서 날짜를 가져 오는 것입니다. datetime을 사용할 때 개체가 하루에 다른 시간에 생성되기 때문에 정확한 개수를 얻지 못할 수 있습니다.

for 루프는 날짜와 개수를 인쇄합니다.


-1

월별 :

 Order.objects.filter().extra({'month':"Extract(month from created)"}).values_list('month').annotate(Count('id'))

연도 별 :

 Order.objects.filter().extra({'year':"Extract(year from created)"}).values_list('year').annotate(Count('id'))

하루 :

 Order.objects.filter().extra({'day':"Extract(day from created)"}).values_list('day').annotate(Count('id'))

Count를 가져 오는 것을 잊지 마십시오

from django.db.models import Count

장고 <1.10


3
네, 좋은 연습은 모델에서 모든를 가져
JC Rocamonde

나는 분명히 아이러니했다. 그렇게하는 것은 끔찍한 관행입니다. 당신은 그것을하지 말아야 나는 (내가하지 않았다) 단지에 대한을 downvoted 것
JC Rocamonde
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.