如何查询重叠的日期范围?

时间:2019-04-02 10:37:24

标签: django django-models

我有在有限时间内分配给团队的成员。成员可以分配给多个团队。成员每天创建工作日志。工作日志分配给成员,该成员分配给团队。中间表“成员资格”存储团队成员的分配日期。

例如,我需要查询与特定团队相关的所有工作日志。在2019年第一季度。团队成员可以在该季度加入和离开,只应考虑团队分配期间的工作日志。

class Teamuser(models.Model):
    key = models.CharField(max_length=200, primary_key=True)
    fullname = models.CharField(max_length=200)

class Team(models.Model):
    id = models.IntegerField(primary_key=True)
    name = models.CharField(max_length=200)
    members = models.ManyToManyField(Teamuser, through='Membership')

class Membership(models.Model):
    id = models.IntegerField(primary_key=True)
    memberid = models.IntegerField(null=False, blank=False)
    teamuser = models.ForeignKey(Teamuser, on_delete=models.CASCADE)
    team = models.ForeignKey(Team, on_delete=models.CASCADE)
    dateFrom = models.DateField(null=True, blank=True, default=None)
    dateTo = models.DateField(null=True, blank=True, default=None)

class Worklog(models.Model):
    worker = models.ForeignKey(Teamuser, on_delete=models.PROTECT)
    day = models.DateField(null=True, blank=True, default=None)
    effort = models.IntegerField()
    comment = models.TextField(null=True)

我尝试首先获取在此时间段内分配的所有用户的列表。

start = date(2019,1,10) 
end = date(2019,1,20) 
Teamuser.objects.filter(Q(membership__team_id=305), Q(membership__dateFrom__range=[start, end]) | Q(membership__dateTo__range=[start, end]) | Q(membership__dateFrom=None) | Q(membership__dateTo=None))  

但是,这不包括在查询日期之前加入团队并且之后离开的人员。没有dateFrom和dateTo的人始终在团队中。

目前我最大的挑战是弄清楚两个日期范围(查询第一季度,团队用户的成员资格)在哪个日期重叠

我希望团队用户在指定时间(2019年1月)分配给查询的团队期间(2019年第一季度)创建的所有工作日志的输出。

2 个答案:

答案 0 :(得分:1)

我发现了一种可行的方法:

start=date(2019, 1, 1)
end=date(2019, 1, 31)

Worklog.objects.filter(
        Q(worker__membership__team_id=305), 
        Q(day__range=[start, end]), 
        Q(day__range=[F('worker__membership__dateFrom'),  F('worker__membership__dateTo')]) |  
        Q(worker__membership__dateFrom=None) | 
        Q(worker__membership__dateTo=None)
    )

基本上限制成员资格中的日期和查询期间。 我以前不知道F()对象,这是关键。

仍要弄清楚是否能涵盖所有情况。

更新

这有效并显示了正确的结果,我根据下面的Endre Both的输入包括了正确的过滤器。该解决方案也可以使用,但是在我较小的数据集上感觉有点慢。我没有测量时间。

Worklog.objects.filter(
        Q(worker__membership__team_id=305), 
        Q(day__range=[start, end]), 
        Q(worker__membership__dateFrom=None) | Q(worker__membership__dateFrom__lte=end),
        Q(worker__membership__dateTo=None) | Q(worker__membership__dateTo__gte=start),
    )

答案 1 :(得分:1)

这可以使您在所考虑的时间段内成为团队成员的用户:

teamusers = (Teamuser.objects
    .filter(
        Q(membership__dateTo__gte=start) | Q(membership__dateTo=None),
        Q(membership__dateFrom__lte=end) | Q(membership__dateFrom=None),
        membership__team_id=305, 
    )
    .distinct()
)

此过滤器将筛选出结束日期为null(大于或等于期间开始)的开始日期,以及开始日期为null(小于或等于结束日期)。

但这对于预过滤工作日志最有用,因为您不能只考虑并在一定时间内完成列表中所有成员的日志。您可能有用户在其成员资格日期之外发布的工作日志。

因此,您必须从日志表开始,对有问题的时间段进行预过滤,如果确定可以提高性能,则可能对上面计算出的团队成员列表进行预过滤。

worklogs = Worklog.objects.filter(day__range=[start, end], worker__in=teamusers)

然后,您使用子查询来检查每条记录,在该特定日期发布该记录的用户是否是该团队的成员:

from django.db.models import Q, OuterRef, Exists

membership_subquery = Teamuser.objects.filter(
    Q(membership__dateTo__gte=OuterRef('day')) | Q(membership__dateTo=None),
    Q(membership__dateFrom__lte=OuterRef('day')) | Q(membership__dateFrom=None),
    membership__team_id=305,
    teamuser=OuterRef('worker'), 
)

worklogs = (worklogs
    .annotate(in_team=Exists(membership_subquery))
    .filter(in_team=True)
)

现在您有了所有有效工作日志的列表,并且可以附加进一步的处理,例如注释聚合等。