示例模型:
class User(models.Model):
pass
class UserStatusChange(models.Model):
user = models.ForeignKey(User, related_name='status_changes')
status = models.CharField()
start_date = models.DateField()
我想使用 end_date
字段注释 UserStatusChanges
查询集,并且 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.
UPD:在 Django 2.0+ 中可以用 Lead Window function 来解决。 在 SQL 中,它会是这样的:
select
user_id, status_id, start_date,
LEAD(start_date, 1) over (partition by user_id order by start_date)
from user_status_change;
最佳答案
您可以使用Subquery()在 Django 1.11 中使用 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_lated 的用户过滤器结合使用时。如果您希望最后一项也有一个有意义的 end_date
,那么您可以使用 Coalesce()
默认值等于当前时间戳。
关于sql - 使用 Django ORM 中的先前对象注释查询集,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47018481/