Django:按月查询组

时间:2016-06-16 06:02:33

标签: python django postgresql

如何在不使用额外费用的情况下按月计算总数?

我目前正在使用:

  • django 1.8
  • postgre 9.3.13
  • Python 2.7

实施例

enter image description here

到目前为止我尝试了什么。

#Doesn't work for me but I don't mind because I don't want to use extra
truncate_month = connection.ops.date_trunc_sql('month','day')
invoices = Invoice.objects.filter(is_deleted = False,company = company).extra({'month': truncate_month}).values('month').annotate(Sum('total'))

----
#It works but I think that it's too slow if I query a big set of data
for current_month in range(1,13):
    Invoice.objects.filter(date__month = current__month).annotate(total = Sum("total"))

还有这个,答案似乎很好,但我无法导入TruncMonth模块。

Django: Group by date (day, month, year)

P.S。我知道这个问题已被多次询问,但我没有看到任何答案。

谢谢!

感谢@ Vin-G的回答。

enter image description here

5 个答案:

答案 0 :(得分:19)

首先,您必须创建一个可以为您提取月份的函数:

from django.db import models
from django.db.models import Func

class Month(Func):
    function = 'EXTRACT'
    template = '%(function)s(MONTH from %(expressions)s)'
    output_field = models.IntegerField()

之后你需要做的就是

  1. 使用月份注释每一行
  2. 使用values()
  3. 按注释月份对结果进行分组
  4. 使用Sum()
  5. 使用总计的汇总总和对每个结果进行注释

    重要:如果您的模型类具有元选项中指定的默认排序,则必须添加空order_by()子句。这是因为https://docs.djangoproject.com/en/1.9/topics/db/aggregation/#interaction-with-default-ordering-or-order-by

      

    在选择输出数据时,将使用查询集的order_by()部分中提到的字段(或在模型上的默认排序中使用的字段),即使它们未在{{ 1}}打电话。这些额外字段用于将“喜欢”结果组合在一起,并且它们可以使其他相同的结果行看起来是分开的。

    如果您不确定,您可以添加空values()子句,无任何不利影响。

    order_by()

    请在此处查看完整要点:https://gist.github.com/alvingonzales/ff9333e39d221981e5fc4cd6cdafdd17

    如果您需要更多信息:

    有关创建自己的Func类的详细信息:https://docs.djangoproject.com/en/1.8/ref/models/expressions/#func-expressions

    有关values()子句的详细信息,(注意它与子句顺序的annotate()交互方式): https://docs.djangoproject.com/en/1.9/topics/db/aggregation/#values

      

    将annotate()和values()子句应用于查询的顺序非常重要。如果values()子句在annotate()之前,则将使用values()子句描述的分组来计算注释。

答案 1 :(得分:5)

result = (
    invoices.objects
        .all()
        .values_list('created_at__year', 'created_at__month')
        .annotate(Sum('total'))
        .order_by('created_at__year', 'created_at__month')
)

答案 2 :(得分:4)

itertools.groupby是Python中的高性能选项,可以与单个数据库查询一起使用:

     if(remarks.matches("^[?]")){
                errors.put("remarks", "Invalid remarks!");
            }

我不知道纯粹的django ORM解决方案。 from itertools import groupby invoices = Invoice.objects.only('date', 'total').order_by('date') month_totals = { k: sum(x.total for x in g) for k, g in groupby(invoices, key=lambda i: i.date.month) } month_totals # {1: 100, 3: 100, 4: 500, 7: 500} 过滤器非常有限,无法在date__monthvalues等中使用。

答案 3 :(得分:1)

我不知道我的解决方案是否比你的更快。你应该剖析它。尽管如此,我只查询db一次而不是12次。

#utils.py
from django.db.models import Count, Sum


def get_total_per_month_value():
    """
    Return the total of sales per month

    ReturnType: [Dict]
    {'December': 3400, 'February': 224, 'January': 792}
    """
    result= {}
    db_result = Sale.objects.values('price','created')
    for i in db_result:
        month = str(i.get('created').strftime("%B"))
        if month in result.keys():
            result[month] = result[month] + i.get('price')
        else:
            result[month] = i.get('price')
    return result

#models.py
class Sale(models.Model):
    price = models.PositiveSmallIntegerField()
    created = models.DateTimeField(_(u'Published'), default="2001-02-24")

#views.py
from .utils import get_total_per_month_value
# ...
result = get_total_per_month_value()

test.py

  #
    import pytest
    from mixer.backend.django import mixer
    #Don't try to write in the database
    pytestmark = pytest.mark.django_db
    def test_get_total_per_month():
        from .utils import get_total_per_month_value
        selected_date = ['01','02','03','01','01']
        #2016-01-12 == YYYY-MM-DD
        for i in selected_date:
            mixer.blend('myapp.Sale', created="2016-"+i+"-12")
        values = get_total_per_month_value() #return a dict
        months = values.keys()
        assert 'January' in months, 'Should include January'
        assert 'February' in months, 'Should include February'
        assert len(months) == 3, 'Should aggregate the months'

答案 4 :(得分:0)

别忘了Django查询集提供了本机datetimes manager,对于带有datetime字段的模型,它可以让您轻松地从所有查询集中拉出所有天/周/月/年。因此,如果上面的Invoice模型有一个created日期时间字段,并且您想要查询集中每个月的总数,则可以执行以下操作:

    invoices = Invoice.objects.all()
    months = invoices.datetimes("created", kind="month")
    for month in months:
        month_invs = invoices.filter(created__month=month.month)
        month_total = month_invs.aggregate(total=Sum("otherfield")).get("total")
        print(f"Month: {month}, Total: {month_total}")

不需要外部功能或部门。