Django:按日期求和,然后创建额外的字段显示滚动平均值

vybvopom  于 2023-05-08  发布在  Go
关注(0)|答案(2)|浏览(117)

我想按日期在模型中创建一个求和,然后添加一个滚动平均值...在一个查询中。这可能吗?
假设我有一个这样的表,叫做“销售”:

|---------------------|------------------|--------------|
|     Date            |     Category     |     Value    |
|---------------------|------------------|--------------|
|      2020-04-01     |         1        |      55.0    |
|---------------------|------------------|--------------|
|      2020-04-01     |         2        |      30.0    |
|---------------------|------------------|--------------|
|      2020-04-02     |         1        |      25.0    |
|---------------------|------------------|--------------|
|      2020-04-02     |         2        |      85.0    |
|---------------------|------------------|--------------|
|      2020-04-03     |         1        |      60.0    |
|---------------------|------------------|--------------|
|      2020-04-03     |         2        |      30.0    |
|---------------------|------------------|--------------|

我想按日期分组(“类别”列不重要),并添加日期值的总和。然后我想添加一个最近7天的滚动平均值。
我试过这个:

days = (
        Sales.objects.values('date').annotate(sum_for_date=Sum('value'))
        ).annotate(
                rolling_avg=Window(
                         expression=Avg('sum_for_date'),
                         frame=RowRange(start=-7,end=0),
                         order_by=F('date').asc(),
                    )
                )
        .order_by('date')

这会抛出此错误:

django.core.exceptions.FieldError: Cannot compute Avg('sum_for_date'): 'sum_for_date' is an aggregate

有什么想法吗

vvppvyoh

vvppvyoh1#

您得到的错误是因为您试图计算同一查询中聚合字段(“sum_for_date”)的平均值。这在Django的ORM中是不可能的。
一种可能的解决方法是使用子查询计算每日总和,然后使用另一个查询计算滚动平均值。下面是一个使用销售模型的示例:

from django.db.models import Avg, Sum, F, Window
from django.db.models.functions import Lag

# Subquery to calculate daily sums
daily_sums = (
    Sales.objects
    .values('date')
    .annotate(sum_for_date=Sum('value'))
)

# Query to calculate rolling average
rolling_avg = (
    daily_sums
    .annotate(
        rolling_sum=Window(
            expression=Sum('sum_for_date'),
            frame=Window(
                expression=Lag('sum_for_date', offset=6),
            ),
        ),
        rolling_count=Window(
            expression=Sum(1),
            frame=Window(
                expression=Lag('date', offset=6),
            ),
        ),
    )
    .annotate(
        rolling_avg=Window(
            expression=F('rolling_sum') / F('rolling_count'),
            frame=Window(
                expression=Lag('date', offset=6),
            ),
        )
    )
    .order_by('date')
)

它首先使用子查询计算每日总和,然后使用窗口函数计算滚动平均值。rolling_sum窗口函数计算前7天的总和,rolling_count窗口函数计算前7天的日期计数。最后,我们将rolling_sum除以rolling_count以获得滚动平均值。
请注意,此方法使用仅在某些数据库中可用的Window函数(例如PostgreSQL)。如果您使用的是不同的数据库,则可能需要使用不同的方法来计算滚动平均值。
关于Window的更多信息:https://docs.djangoproject.com/en/dev/ref/models/expressions/#window-functions

91zkwejq

91zkwejq2#

出现错误的原因是您试图计算同一查询中聚合字段(“sum_for_date”)的平均值。若要解决此问题,可以使用子查询来计算滚动平均值。
你可以试试这样的

from django.db.models import Sum, Avg, F, Window
from django.db.models.functions import TruncDate

window_size = 7

# calculate the sum of values for each date
sums_by_date = (
    Sales.objects
    .annotate(date=TruncDate('date'))
    .values('date')
    .annotate(total=Sum('value'))
)

# calculate the rolling sum and average using Window functions
rolling_window = Window(
    expression=Sum('value').over(order_by=F('date').asc(), range=(1 - window_size, 0)),
    frame=Window().rows_between(start=-window_size, end=0),
)
rolling_avg = (
    Sales.objects
    .annotate(date=TruncDate('date'))
    .values('date')
    .annotate(
        total=Sum('value'),
        rolling_sum=rolling_window,
        rolling_avg=Avg('value').over(order_by=F('date').asc(), range=(1 - window_size, 0)),
    )
    .values('date', 'total', 'rolling_sum', 'rolling_avg')
    .order_by('date')
)

# retrieve the results
results = list(rolling_avg)

相关问题