Django:按日期分组(日,月,年)


89

我有一个像这样的简单模型:

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

我想输出按月细分:

  • 一个月内有多少笔交易(COUNT
  • 合并值(SUM

我不确定最好的攻击方法是什么。我已经看到了一些看上去很吓人的选择查询,但我的简单想法是告诉我,最好是迭代数字,从任意开始的年/月开始,一直计数到直到本月为止,然后简单地抛出查询该月的过滤条件。更多的数据库工作-减轻开发人员的压力!

什么对您最有意义?有什么好方法可以拉回快速数据表吗?还是我的肮脏方法可能是最好的主意?

我正在使用Django 1.3。不知道他们最近是否添加了更好的方法GROUP_BY


Answers:


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
您正在使用什么数据库后端-在postgres中可以正常工作>>> qs.extra({'month':td}).values('month').annotate(Sum('total')) [{'total__sum': Decimal('1234.56'), 'month': datetime.datetime(2011, 12, 1, 0, 0)}]
tback

1
@seddonym修复(感谢jarshwah)
退回

1
Truncmonth在Django 1.8中不可用
Sudhakaran Packianathan

2
谢谢,效果很好。1.10之前版本的特殊情况:如果一个连接/过滤器可能具有相同字段(例如,时间戳记)在其他模型上,则必须完全限定该字段'{}.timestamp'.format(model._meta.db_table)
zsepi

1
请注意,如果Django USE_TZ设置为True,则这两个版本并不完全相同。版本using TruncMonthTIME_ZONE在截断之前将时间戳转换为设置指定的时区,而版本using date_trunc_sql将截断数据库中的原始UTC时间戳。
丹尼尔·哈丁'18

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
是的:docs.djangoproject.com/zh-CN/1.11/topics/db/aggregation / ... ...虽然感觉不佳,但是他们对那些django的人非常聪明,实际上是这样。
威廉姆斯

TruncDate允许您按日期(月份的天)分组
Neil

10

另一种方法是使用ExtractMonth。由于仅返回一个datetime年值,因此在使用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

@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

可能有更好的循环年/月的方式,但这并不是我真正关心的:)


顺便说一句,它可以正常工作,但您知道循环数月也不是一个好主意。如果有人想在一个月的一天内完成该操作,那么此循环将重复30-31天。否则工作正常
Mayank Pratap Singh '18

如果您有数百万条记录,这太慢了
jifferent

@jifferent绝对!我添加了它以显示发布问题时我的解决方案。其他答案要好得多。
奥利

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

别忘了导入Count

from django.db.models import Count

对于Django <1.10


3
是的,好的做法是从模型中全部导入
JC Rocamonde

我显然很讽刺。这样做是一种可怕的做法。您不应该这样做,我会为此而
投票
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.