示例模型:
class User(models.Model):
pass
class UserStatusChange(models.Model):
user = models.ForeignKey(User, related_name='status_changes')
status = models.CharField()
start_date = models.DateField()
我想使用UserStatusChanges
字段对end_date
个查询集进行注释,end_date
应该等于同一用户下次状态更改的start_date
。
最终,我希望能够做到这一点:
qs = UserStatusChange.ojects.annotate(end_date=???)
qs = qs.filter(start_date__lte=some_date, end_date__gte=another_date)
逻辑上,注释应该是这样的:
qs.annotate(
end_date=qs.filter(
user=OuterRef('user'),
start_date__gt=OuterRef('start_date')
).order_by('start_date').first().start_date)
但如果可能的话,它应该是一个数据库查询。
解决方案:
subquery = UserStatusChange.objects.filter(user=OuterRef('user'),
start_date__gt=OuterRef('start_date')).order_by('start_date')
UserStatusChange.objects.annotate(end_date=Subquery(subquery.values('start_date')[:1]))
这很有效,感谢@ hynekcer的回答。但是aggregate
我得到了错误:
ValueError: This queryset contains a reference to an outer query and may only be used in a subquery.
我不确定,也许有更好的解决方案。但是这个特殊的解决方案解决了我的问题。
答案 0 :(得分:2)
您可以在Django 1.11中使用Subquery()和OuterRef()。
from django.db.models import Min, OuterRef, Subquery
from django.db.models.functions import Coalesce
default_end = now() # or the end of the recorded history
qs = (
UserStatusChanges.objects
.annotate(
end_date=Coalesce(
Subquery(
UserStatusChanges.objects
.filter(
user=OuterRef('user'),
start_date__gt=OuterRef('start_date')
)
.order_by()
.aggregate(Min('start_date'))
),
default_end
)
)
)
qs = qs.order_by('user', 'start_date')
# an optional filter
qs = qs.filter(start_date__lte=some_date, end_date__gte=another_date, user__in=[...])
在执行时将其编译为一个查询,例如与prefetch_related结合用户过滤器时。如果您还希望最后一项有意义的end_date
,那么您可以使用Coalesce()
,其默认值等于当前时间戳。