Django ORM:获取每个类别的每月平均价格
Django ORM: get the monthly average of price for each category
想象一下这个简单的模型:
class Expense(models.Model):
price = models.DecimalField(decimal_places=2, max_digits=6)
description = models.CharField(max_length=300)
category = models.CharField(choices=ExpenseCategory.choices, max_length=20)
created_at = models.DateField()
我正在尝试获取当年每个 category
的月平均值 price
。我的一般想法是做类似的事情:
sub = (
Expense.objects.filter(created_at__year=date.today().year)
.annotate(month=TruncMonth("created_at"))
.values("month", "category")
.annotate(total=Sum("price"))
.order_by("month")
)
qs = Expense.objects.values("category").annotate(avg=Avg(Subquery(sub.values("total"))))
我主要是想:
- 截断
created_at
的月份
- 按
category
和 month
分组
- 求和
prices
- 为每个
category
聚合 prices
如果我愿意,它工作得很好:
for category in categories:
sub.filter(category=category).aggregate(avg=Avg("total"))
模型管理器中的一个函数可能是一个很好的解决方案。在您的 models.py
中,在您的 Expense
class:
之前添加一个名为 ExpenseManager
的 models.Manager class
class ExpenseManager(models.Manager):
def summarise_categories(self):
return (
super(ExpenseManager, self)
.get_queryset()
.filter(created_at__year=date.today().year)
.annotate(month=TruncMonth("created_at"))
.values("month", "category")
.annotate(total=Sum("price")/Count("created_at__month", distinct=True))
)
然后在你 Expense
class 做:
class Expense(models.Model):
...
objects = ExpenseManager()
要获得生成的查询集聚合,您只需在视图中的模型对象上调用模型管理器方法即可:
summarised_categories = Expense.objects.summarise_categories()
这种方法将所有工作推送到数据库,返回类别字典及其 year-to-date 的平均每月价格。
您的查询可能比您想象的要简单。您当前的解决方案尝试是:
- 截断
created_at
以获得月份
- 按
category
和 month
分组
- 总和
prices
- 取每个类别总和的平均值
这个问题是采用聚合的聚合。让我们反过来考虑您的问题(我们将在这里做一些数学运算)。你想要一个类别的月平均价格,如果我们只考虑一个类别并且每月价格是一个数组M[12]
,那么我们可以表示为:
(M[0] + M[1] + ... + M[11]) / 12
M 中的每个值都可以被认为是 prices
的总和,其中月份匹配。如果我们将 P[12][] 视为包含每个月价格的二维数组,我们可以将上面的公式重写为:
(Sum(P[0]) + Sum(P[1] + ... + Sum(P[12])) / 12
进一步考虑,它只是一年中所有价格的总和除以 12!这意味着您的查询可以简单地写成:
from django.db.models import ExpressionWrapper, FloatField, Sum, Value
qs = Expense.objects.filter(
created_at__year=date.today().year
).values("category").annotate(
avg=ExpressionWrapper(
Sum("price") / Value(12), output_field=FloatField()
)
)
注意:除以 12 意味着我们假设我们有全年的数据,这可能不适用于当前年份,因此我们应该除以适当的月数。我们可能还想过滤到上个月,以防我们没有明显进入当前月份。
想象一下这个简单的模型:
class Expense(models.Model):
price = models.DecimalField(decimal_places=2, max_digits=6)
description = models.CharField(max_length=300)
category = models.CharField(choices=ExpenseCategory.choices, max_length=20)
created_at = models.DateField()
我正在尝试获取当年每个 category
的月平均值 price
。我的一般想法是做类似的事情:
sub = (
Expense.objects.filter(created_at__year=date.today().year)
.annotate(month=TruncMonth("created_at"))
.values("month", "category")
.annotate(total=Sum("price"))
.order_by("month")
)
qs = Expense.objects.values("category").annotate(avg=Avg(Subquery(sub.values("total"))))
我主要是想:
- 截断
created_at
的月份
- 按
category
和month
分组
- 求和
prices
- 为每个
category
聚合
prices
如果我愿意,它工作得很好:
for category in categories:
sub.filter(category=category).aggregate(avg=Avg("total"))
模型管理器中的一个函数可能是一个很好的解决方案。在您的 models.py
中,在您的 Expense
class:
ExpenseManager
的 models.Manager class
class ExpenseManager(models.Manager):
def summarise_categories(self):
return (
super(ExpenseManager, self)
.get_queryset()
.filter(created_at__year=date.today().year)
.annotate(month=TruncMonth("created_at"))
.values("month", "category")
.annotate(total=Sum("price")/Count("created_at__month", distinct=True))
)
然后在你 Expense
class 做:
class Expense(models.Model):
...
objects = ExpenseManager()
要获得生成的查询集聚合,您只需在视图中的模型对象上调用模型管理器方法即可:
summarised_categories = Expense.objects.summarise_categories()
这种方法将所有工作推送到数据库,返回类别字典及其 year-to-date 的平均每月价格。
您的查询可能比您想象的要简单。您当前的解决方案尝试是:
- 截断
created_at
以获得月份 - 按
category
和month
分组
- 总和
prices
- 取每个类别总和的平均值
这个问题是采用聚合的聚合。让我们反过来考虑您的问题(我们将在这里做一些数学运算)。你想要一个类别的月平均价格,如果我们只考虑一个类别并且每月价格是一个数组M[12]
,那么我们可以表示为:
(M[0] + M[1] + ... + M[11]) / 12
M 中的每个值都可以被认为是 prices
的总和,其中月份匹配。如果我们将 P[12][] 视为包含每个月价格的二维数组,我们可以将上面的公式重写为:
(Sum(P[0]) + Sum(P[1] + ... + Sum(P[12])) / 12
进一步考虑,它只是一年中所有价格的总和除以 12!这意味着您的查询可以简单地写成:
from django.db.models import ExpressionWrapper, FloatField, Sum, Value
qs = Expense.objects.filter(
created_at__year=date.today().year
).values("category").annotate(
avg=ExpressionWrapper(
Sum("price") / Value(12), output_field=FloatField()
)
)
注意:除以 12 意味着我们假设我们有全年的数据,这可能不适用于当前年份,因此我们应该除以适当的月数。我们可能还想过滤到上个月,以防我们没有明显进入当前月份。