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 every
UserStatusChanges
与某个特定用户相关。因此,当我想用
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
。我希望你能把问题的口头部分弄清楚,我可以把它投给别人,认为它对其他人有用,并删除我的评论。