如何在不使用额外费用的情况下按月计算总数?
我目前正在使用:
实施例
到目前为止我尝试了什么。
#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的回答。
答案 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()
之后你需要做的就是
values()
Sum()
重要:如果您的模型类具有元选项中指定的默认排序,则必须添加空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__month
,values
等中使用。
答案 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}")
不需要外部功能或部门。