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


89

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

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

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

  • 1か月の販売数(COUNT
  • 合計値(SUM

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

あなたにとって最も意味のあるものは何ですか?データの簡単な表をプルバックできる良い方法はありますか?それとも私の汚い方法はおそらく最良のアイデアですか?

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


回答:


219

Django 1.10以降

Djangoのドキュメントには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')

編集

  • 追加された数
  • djangoの追加情報> = 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に感謝)
2016年

1
TruncmonthはDjango 1.8では使用できません
Sudhakaran Packianathan 2016年

2
おかげで、うまくいきます。1.10より前のバージョンのコーナーケース:同じフィールド(例:タイムスタンプ)を持つ可能性がある他のモデルで結合/フィルターする場合、フィールドを完全に修飾する必要があります'{}.timestamp'.format(model._meta.db_table)
zsepi

1
Django USE_TZ設定がのTrue場合、2つのバージョンは完全に等価ではないことに注意してください。バージョンを使用TruncMonthすると、タイムスタンプがTIME_ZONE設定で指定されたタイムゾーンに変換されてから切り捨てdate_trunc_sqlられますが、バージョンを使用すると、データベース内の生のUTCタイムスタンプが切り捨てられます。
ダニエルハーディング

32

@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

10

別のアプローチはを使用することExtractMonthです。返されるdatetimeの年の値が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')  

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

これquerysetは注文のリストで、月額1行で、売上の合計を組み合わせています。sales_sum

@Django 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

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


ところでそれはうまく動作しますが、何ヶ月にも渡るループも素晴らしいアイデアではありません。誰かが1日に作成したい場合、このループは30〜31日繰り返されます。それ以外の場合は正常に機能
Mayank Pratap Singh 2018

数百万のレコードがある場合、これは遅すぎます
19:19の

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

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

カウントをインポートすることを忘れないでください

from django.db.models import Count

ジャンゴ<1.10


3
ええ、素晴らしい練習です。モデルからすべてインポートします
JC Rocamonde

皮肉なのは明らかだった。それをするのは恐ろしい習慣です。あなたはそれをすべきではなく、私はそのためだけに反対票を投じていたでしょう(私はしませんでした)
JC Rocamonde
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.