使用Django ORM进行快速移动平均计算

时间:2018-02-14 15:07:20

标签: django postgresql django-models django-orm django-postgresql

我们运行Postgres 9.6.5和Django 2.0。我们有Model字段created_atvalue。我们需要计算某个date_range的90天移动平均线。这就是我们这样做的方式:

output = []

for i in range(len(date_range)):
    output.append(
        Model.objects.filter(
            created_at__date__range=(date_range[i]-timezone.timedelta(days=90), date_range[i]),
        ).aggregate(Avg('value'))['value__avg'].days
    )

这会使用Avg汇总功能,因此速度相当快,但我们需要对date_range中的每个日期进行一次查询。对于更长的范围,这意味着很多查询。

Postgres can do this in a single query。我的问题是 - 我们可以在使用Django ORM的单个查询中以某种方式执行此操作吗?

(我知道我可以用Django ORM执行原始SQL,但我想尽可能避免这种情况,这就是我要问的原因。)

3 个答案:

答案 0 :(得分:1)

假设您每个日期只有一个条目,您可以使用Django 2.0的新窗口表达式来计算单个查询中的90个周期移动平均值:

from django.db.models import Avg, F, RowRange, Window

items = Model.objects.annotate(
    avg=Window(
        expression=Avg('value'), 
        order_by=F('created_at').asc(), 
        frame=RowRange(start=-90,end=0)
    )
)

另请参阅ValueRange,如果您希望按特定字段值进行框架设置,例如,如果每个特定日期有多行,则可能会派上用场。

答案 1 :(得分:0)

您可以使用annotation而不是聚合。考虑到这一点,我开始测试,我不完全确定下面的代码。另请参阅有关F()对象

的文档
    Model.objects.annotate(
        value_avg=Avg(
            'value',
            filter=Q(
                created_at__date__range=(
                    F('created_at__date')-timezone.timedelta(days=90),
                    F('created_at__date')
                )
            )
        )
    )

your_date_field取决于你

答案 2 :(得分:0)

另一个尝试。这样性能更高,因为它只使用一个查询,但是从DB中获取所有必需的Model-instance,以在python而不是DB级别中执行逻辑。仍然不是最优的,但希望这次它能做正确的事情;)你必须比较它是否能真正改善你的情况。

import numpy as np
instances =  Model.objects.filter(
        created_at__gte=min(date_range)-timezone.timedelta(days=90),
        created_at__lte=max(date_range)
    ).values('created_at', 'value')

instances = list(instances)  # evaluate QuerySet and hit DB only once

output = []
for i in range(len(date_range)):    
    output.append(
        np.mean(np.array([inst.value for inst in instances if \
            inst.created_at >= date_range[i]-timezone.timedelta(days=90) and \
            inst.created_at <  date_range[i]
        ]))
    )