根据下面的Django模型,一辆汽车沿着特定的道路行驶,其开始和结束时间如下:

class Travel(models.Model):
  car = models.CharField()
  road = models.CharField()
  start = models.DateTimeField()
  end = models.DateTimeField()

我想确定与目标车X在同一条道路上至少行驶了m分钟的一组车X.

我应该如何获得所需的车X?

My attempt:

假设我使用过滤来获得x所处的旅行集T.

<--Travel.objects.filter(car=x)

然后我用暴力:

for t in T:
  possible_travels <-- filter Travel.objects with car=/=x, road=t.road, start < t.end, end > t.start 
  
  confirmed_travels <-- further filter possible_travels with the overlapping region being at least m minutes long

  confirmed_cars <-- confirmed_travels.values('cars').distinct()

然而,问题是:

  1. 通过在循环中查询,它可能会涉及许多数据库命中.
  2. 此外,确认车会给出一个QuerySet对象.所以我似乎需要以某种方式将这些QuerySet对象附加在一起.我看到其他帖子在做一些事情,比如转换为列表,然后添加,最后转换回QuerySet,但有些人说这不是一个好方法,我应该这样做吗?

有更好的方法吗?for循环真的有必要吗?我能完全避免吗?

编辑:

推荐答案

我们可以有两种方法:

  1. 这是我的解决方案,有查询集,没有任何循环.为了找到start次和end次之间的交集,我将查询集分为4类:
  • start1->;开始2->;end1->;end2(前面的q_幻灯片)(交叉点=end1-start2)
  • start1->;开始2->;end2->;end1(q_包含)(交叉点=end2-起点2)
  • 开始2->;start1->;end1->;end2(包含q_)(交叉点=end1-start1)
  • 开始2->;start1->;end2->;end1(q_幻灯片后)(交叉点=end2-start1)
from django.db.models import OuterRef, ExpressionWrapper,\
                             F, Q, functions, DurationField, FloatField

# Use this function instead of "annotate_delta" if your db supports DurationField (postgres supports this but sqlite does not)
def annotate_delta_with_duration_support(qs, start, end):
    duration_exp = ExpressionWrapper(end - start, output_field=DurationField())
    return qs.annotate(delta=functions.ExtractMinute(duration_exp))


def annotate_delta(qs, start, end):
    duration_exp = ExpressionWrapper((end - start) / (60 * 10**6),
                                     output_field=FloatField())
    return qs.annotate(delta=duration_exp)


x = 'mycar'
m = 20

q_is_contained = Q(start__gte=OuterRef('start')) & Q(end__lte=OuterRef('end'))
qs = annotate_delta(Travel.objects, F('start'), F('end'))
qs = qs.filter(q_is_contained, delta__gte=m, car=x, road=OuterRef('road'))
res1 = Travel.objects.exclude(car=x).annotate(ex=Exists(qs)).filter(ex=True)


q_contains = Q(start__lte=OuterRef('start')) & Q(end__gte=OuterRef('end'))
qs = annotate_delta(Travel.objects, OuterRef('start'), OuterRef('end'))
qs = qs.filter(q_contains, delta__gte=m, car=x, road=OuterRef('road'))
res2 = Travel.objects.exclude(car=x).annotate(ex=Exists(qs)).filter(ex=True)


q_slides_before = Q(start__lte=OuterRef('start')) & \
                  Q(end__lte=OuterRef('end')) & \
                  Q(end__gte=OuterRef('start'))
qs = annotate_delta(Travel.objects, OuterRef('start'), F('end'))
qs = qs.filter(q_slides_before, delta__gte=m, car=x, road=OuterRef('road'))
res3 = Travel.objects.exclude(car=x).annotate(ex=Exists(qs)).filter(ex=True)


q_slides_after = Q(start__gte=OuterRef('start')) & \
                 Q(end__gte=OuterRef('end')) & \
                 Q(start__lte=OuterRef('end'))
qs = annotate_delta(Travel.objects, F('start'), OuterRef('end'))
qs = qs.filter(q_slides_after, delta__gte=m, car=x, road=OuterRef('road'))
res4 = Travel.objects.exclude(car=x).annotate(ex=Exists(qs)).filter(ex=True)

res = res1 | res2 | res3 | res4

  1. 感谢@SUTerliakov,我发现还有一个更简洁的解决方案:
from django.db.models import OuterRef, ExpressionWrapper,\
                             F, functions, DurationField, FloatField


# Use this function instead of "annotate_delta" if your db supports DurationField (postgres supports this but sqlite does not)
def annotate_delta_with_duration_support(qs, start, end):
    duration_exp = ExpressionWrapper(end - start, output_field=DurationField())
    return qs.annotate(delta=functions.ExtractMinute(duration_exp))


def annotate_delta(qs, start, end):
    duration_exp = ExpressionWrapper((end - start) / (60 * 10**6),
                                     output_field=FloatField())
    return qs.annotate(delta=duration_exp)


x = 'mycar'
m = 20

qs = annotate_delta(Travel.objects, functions.Greatest(F('start'), OuterRef('start')),
                    functions.Least(F('end'), OuterRef('end')))
qs = qs.filter(q_slides_after, delta__gte=m, car=x, road=OuterRef('road'))
res = Travel.objects.exclude(car=x).annotate(ex=Exists(qs)).filter(ex=True)

Django相关问答推荐

如何在Django中将字段及其数据从一个模型添加到另一个模型?

Django后端对印前判断请求未通过访问控制判断给我以下回应:否访问控制允许来源

没有与给定查询匹配的监视列表

从url参数过滤的Django日期范围返回空查询集

root urls.py 是 Django 中的 config/urls.py 吗?

使用django提交后如何保留html表单数据?

在生产中使用 Django 中的 SQLite?

django excel xlwt

在 Django 应用程序之间共享模型

Django JavaScript 文件

Django 存储匿名用户数据

Django 不调用模型清理方法

has_object_permission 和 has_permission 有什么区别?

如何从 Django 的 TabularInline 管理视图中省略对象名称?

AssertionError: `HyperlinkedIdentityField` 需要序列化程序上下文中的请求

如何使用查询参数构造 Django 反向/url?

Django将HttpResponseRedirect返回到带有参数的url

Table doesn't exist表不存在

如何在 django 中仅获取表的特定列?

django.urls.path中name参数的作用是什么?