Django: Tarihe göre gruplama (gün, ay, yıl)


94

Bunun gibi basit bir Modelim var:

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

Ve aşağıdakilerin aylık dökümünü çıkarmak istiyorum:

  • Bir ayda kaç satış vardı ( COUNT)
  • Birleşik değer ( SUM)

Buna saldırmanın en iyi yolunun ne olduğundan emin değilim. Oldukça ürkütücü görünen ekstra seçkin sorgular gördüm, ancak basit zihnim bana, rastgele bir başlangıç ​​yılı / ayından başlayarak ve içinde bulunduğum aya ulaşana kadar sayıları yinelemekten daha iyi olabileceğimi söylüyor. o ay için filtreleme sorguları. Daha fazla veritabanı çalışması - daha az geliştirici stresi!

Size en mantıklı gelen nedir? Hızlı bir veri tablosunu geri almanın güzel bir yolu var mı? Yoksa kirli yöntemim muhtemelen en iyi fikir mi?

Django 1.3 kullanıyorum. GROUP_BYSon zamanlarda daha güzel bir yol ekleyip eklemediklerinden emin değilim .


Yanıtlar:


225

Django 1.10 ve üstü

Django dokümantasyon listeleri extraolarak yakında kullanılmayacak . (@Seddonym, @ Lucas03'e işaret ettiğiniz için teşekkürler). Bir bilet açtım ve bu jarshwah'ın sağladığı çözüm.

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 

eski versiyonlar

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')

Düzenlemeler

  • Sayım eklendi
  • Django> = 1.10 için bilgi eklendi

1
hangi veritabanı arka >>> qs.extra({'month':td}).values('month').annotate(Sum('total')) [{'total__sum': Decimal('1234.56'), 'month': datetime.datetime(2011, 12, 1, 0, 0)}]
ucunu

1
@seddonym Sabitlendi (jarshwah sayesinde)
tback

1
Truncmonth, Django 1.8
Sudhakaran Packianathan'da

2
teşekkürler, harika çalışıyor. 1.10 öncesi sürüm için köşe durum: eğer biri aynı alana sahip olabilecek diğer modellere '{}.timestamp'.format(model._meta.db_table)
katılırsa

1
Django USE_TZayarı ise True, iki sürümün tam olarak eşdeğer olmadığına dair kısa bir not . Kullanan sürüm TruncMonth, zaman damgasını TIME_ZONEkesmeden önce ayar tarafından belirtilen saat dilimine dönüştürürken, kullanılan sürüm date_trunc_sql, veritabanındaki ham UTC zaman damgasını keser.
Daniel Harding

36

@Tback yanıtına sadece küçük bir ekleme: Django 1.10.6 ve postgres ile benim için işe yaramadı. Düzeltmek için sonuna order_by () ekledim.

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/… ... iyi bir tasarım gibi gelmiyor ama django adamları çok akıllılar , bu yüzden aslında öyle.
Williams

TruncDatetarihe göre gruplamanıza olanak sağlar (ayın günü)
Neil

11

Başka bir yaklaşım kullanmaktır ExtractMonth. Döndürülen yalnızca bir tarih / saat değeri nedeniyle TruncMonth kullanırken sorunla karşılaştım. Örneğin, yalnızca 2009'daki aylar iade ediliyordu. ExtractMonth bu sorunu mükemmel bir şekilde çözdü ve aşağıdaki gibi kullanılabilir:

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')

querysetSatışların toplamını birleştirerek Düzenin bir liste, ayda bir satır vardır:sales_sum

@Django 2.1.7


1

İşte benim kirli yöntemim. Kirli.

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

Yılları / ayları döngüye sokmanın daha iyi bir yolu olabilir ama benim gerçekten önemsediğim şey bu değil :)


BTW İyi çalışacak, ancak aylar süren döngü de harika bir fikir değil. Ya birisi bunu bir ayın gününde yapmak isterse, bu döngü 30-31 gün arasında yinelenecektir. aksi halde işe yarıyor
Mayank Pratap Singh

milyonlarca
kaydınız

@jifferent Kesinlikle! Soruyu gönderirken çözümümün ne olduğunu göstermek için ekledim. Diğer cevaplar çok daha iyi.
Oli

0

Verileri rastgele zaman dilimlerine göre nasıl gruplayabileceğiniz aşağıda açıklanmıştır:

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

Veritabanımda sipariş tablom var. Son 3 aydaki siparişleri aylık olarak sayacağım

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 alanıdır. ekstra fonksiyon tarafından yapılan şey tarih saat değerlerinden tarih almaktır. datetime kullanırken, nesneler bir günün farklı saatlerinde oluşturulduğundan, sayımı doğru alamayabiliriz.

For döngüsü, tarihi ve sayım sayısını yazdıracaktır


-1

Ayda:

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

Yıla göre:

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

Güne göre:

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

Count içe aktarmayı unutmayın

from django.db.models import Count

Django <1.10 için


3
Evet, harika bir uygulama, hepsini modellerden içe aktar
JC Rocamonde

Açıkça ironik davrandım. Bunu yapmak korkunç bir uygulamadır. Bunu yapmamalısın ve ben de bunun için (ki ben yapmadım)
olumsuz oy verirdim
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.