我正在处理的应用程序有一个时间表,可以分配工作人员在不同的时间段在不同的地点工作。时间表通过
ChangeSet
模型进行版本控制。每个 ScheduleEntry
引用一个 ChangeSet
.
class Worker(models.Model):
name = models.CharField(max_length=100)
class Location(models.Model):
name = models.CharField(max_length=100)
class TimeBlock(models.Model)
start_date = models.DateField()
end_date = models.DateField()
class ChangeSet(models.Model):
published_on = models.DateTimeField(null=True)
class ScheduleEntry(models.Model):
change_set = models.ForeignKey(ChangeSet, on_delete=models.CASCADE)
worker = models.ForeignKey(Worker, on_delete=models.CASCADE)
time_block = models.ForeignKey(TimeBlock, on_delete=models.CASCADE)
location = models.ForeignKey(Location, null=True, on_delete=models.CASCADE)
每个时间块只能分配一个工人到一个位置。该位置由具有该工人和时间块的最新非空
ScheduleEntry
的change_set.published_on
确定。
A
Worker
可能是未安排的(有 None
作为他们的位置)要么是因为在那个时间段内没有工作人员的任何计划条目,条目都链接到未发布的更改集,或者是最近发布的条目指定 null
作为 location
.
获取工作人员在特定时间段内当前发布的时间表位置很容易:
ScheduleEntry.objects.filter(
change_set__published_on__isnull=False,
worker=my_worker,
time_block=my_time_block,
).order_by('-change_set__published_on').first()
但是,我无法弄清楚如何执行查询以获取以下内容:
换句话说,我想要一个查询,该查询返回属于已发布更改集的所有计划条目,并且没有被具有更新的
change_set.published_on
值的同一 worker/time_block 对的后续条目覆盖。一旦有了它,我就可以使用过滤器进一步细化查询,以按位置、时间段或人员获取条目。
我想对查询应用
.distinct('worker', 'time_block')
,但是传递字段名称是 PostgreSQL 的唯一功能,我目前正在使用 SQLite。
这是可以用 SQL 和 Django ORM 高效表达的东西吗?
对于任何后来发现这个问题的人,这是我最终得出的解决方案:
from django.db.models import OuterRef, Exists
ScheduleEntry.objects.filter(
change_set__published_on__isnull=False,
).exclude(
Exists(
ScheduleEntry.objects.filter(
worker=OuterRef('worker'),
time_block=OuterRef('time_block'),
change_set__published_on__isnull=False,
change_set__published_on__gt=OuterRef('change_set__published_on'),
)
)
)
外部查询首先过滤记录以仅包括那些属于已发布更改集的记录 (
change_set__published_on__isnull=False
)。如果内部结果为任何记录 (.exclude
),则它会排除记录 (Exists
)。
内部查询在模型中查找与外部查询 (
worker
) 具有相同 worker=OuterRef('worker')
的任何记录,相同的 time_block
,即已发布的更改集的一部分 (.._isnull=False
) 和 最关键的是 是后来更改集的一部分 (change_set__published_on__gt=OuterRef('change_set__published_on')
)。
实际上,它的作用是排除在后续更改集中被“覆盖”的条目,从而只保留每个条目的最新版本。