Sql 用Django ORM中的前一个对象注释queryset
示例模型:Sql 用Django ORM中的前一个对象注释queryset,sql,django-queryset,django-orm,Sql,Django Queryset,Django Orm,示例模型: 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\u date字段注释UserStatusChanges
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\u date
字段注释UserStatusChanges
queryset,并且end\u date
应该等于同一用户下一次状态更改的start\u 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)
select
user_id, status_id, start_date,
LEAD(start_date, 1) over (partition by user_id order by start_date)
from user_status_change;
但如果可能的话,它应该是一个DB查询
解决方案:
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+中,它可以通过以下方法解决。
在SQL中,它将如下所示:
qs.annotate(
end_date=qs.filter(
user=OuterRef('user'),
start_date__gt=OuterRef('start_date')
).order_by('start_date').first().start_date)
select
user_id, status_id, start_date,
LEAD(start_date, 1) over (partition by user_id order by start_date)
from user_status_change;
可以在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=[...])
它在执行时编译为一个查询,例如通过预取与用户筛选相结合时。如果您希望最后一项也有一个有意义的
结束日期
,那么您可以使用与当前时间戳相同的默认值。您想要得到什么?你所说的user=F('user')是什么意思?它将被编译为
app\u userstatusshange.user=app\u userstatusshange.user`这可能不是您所期望的。@hynekcer我想为同一用户选择状态更改。@hynekcer everyUserStatusChanges
与某个特定用户相关。因此,当我想用end_date
注释UserStatusChanges
时,end_date
应该与某些用户先前的更改相关。所以,最后,我必须这样做:qs=UserStatusChanges.ojects.annotate(end\u date=?)
qs=qs.filter(start\u date\uu lte=some\u date,end\u date\uu gte=another\u date)
与User.objects.prefetch相关的(prefetch('status\u changes',queryset=qs))
这意味着我需要获得按日期范围过滤的用户更改事件列表。是否可以结束日期
?我是按照您编写的方式实现它的,但出于提问的目的,我的意思是重命名为“上一个日期”或将结束日期定义为下一个状态的日期。如果您希望注释end\u date
“与某个用户的上一次更改相关”,则它小于start\u date
。我希望你能把问题的口头部分弄清楚,我可以把它投给别人,认为它对其他人有用,并删除我的评论。