web-dev-qa-db-ja.com

Django:日付(日、月、年)でグループ化

私はこのような単純なモデルを持っています:

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

そして、私は月ごとの内訳を出力したい:

  • 1か月に販売された数(COUNT
  • 結合された値(SUM

これを攻撃する最善の方法は何なのかわかりません。かなり怖い外観の余分な選択クエリを見てきましたが、単純な心では、任意の開始年/月から始まり、現在の月に達するまでカウントアップして、単純な数字を繰り返した方が良いかもしれないと教えられていますその月のフィルタリングをクエリします。より多くのデータベース作業-開発者のストレスを軽減!

あなたにとって何が最も理にかなっていますか?データの簡単な表を引き出すことができる素敵な方法はありますか?それとも私の汚い方法がおそらく最良のアイデアですか?

私はDjango 1.3を使用しています。最近、GROUP_BYにより良い方法を追加したかどうかはわかりません。

74
Oli

Django 1.10以降

Djangoのドキュメントには、extra非推奨としてリストされています。 (@ seddonym、@ Lucas03を指摘してくれてありがとう)。 ticket を開きましたが、これは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')

編集

  • 追加カウント
  • Django> = 1.10の情報を追加
200
tback

@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()
22
Rani

別のアプローチは、ExtractMonthを使用することです。返される日時の値が1つだけであるため、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')  
6
Turtle
    metrics = {
        'sales_sum': Sum('total'),
    }
    queryset = Order.objects.values('created__month')
                               .annotate(**metrics)
                               .order_by('created__month')

querysetは、1か月に1行の注文のリストで、売上の合計を組み合わせます:sales_sum

@ Django 2.1.7

0
C.K.

任意の期間ごとにデータをグループ化する方法は次のとおりです。

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
Max Malysh

これが私の汚い方法です。これは汚れた。

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

年/月をループするより良い方法があるかもしれませんが、それは本当に私が気にすることではありません:)

0
Oli