我们运行Postgres 9.6.5和Django 2.0。我们有Model
字段created_at
和value
。我们需要计算某个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,但我想尽可能避免这种情况,这就是我要问的原因。)
答案 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]
]))
)