我正在构建Django应用程序,旨在为10K +人(以及将来更多)维护时间表(时间表)。基本上,问题陈述如下:每个人都有单独的时间表,下一年有免费插槽。它是离散的,步骤为15分钟。我需要开发模型架构(这将暗示下面的数据库设计)来执行以下操作:
例如,我有11月14日上午8点至14点免费的John和11月14日上午10点至11点免费的Sara。如果我查询John的空闲时段,我想要“11月14日上午8点到14点”。如果我查询“从上午8点到上午11点的自由人”,我得到约翰,因为萨拉直到上午10点才自由。如果我查询“从上午10点到上午11点的自由人”,我想得到John和Sara。 我想到了这个问题,我的想法如下。
解决方案№1:我们创建一个模型FreeTimeSlot,它可以存储关于每15分钟时间间隔的信息,并建立与人的关系。
class Person(models.Model):
name = models.CharField(max_length=32, null=False, blank=False)
free_slots = models.ManyToManyField(FreeTimeSlot, related_name='tutor_set', null=True, blank=True, through='PersonSlot')
class TimeSlot(models.Model):
time = models.DateTimeField(db_index=True) #perhaps other field type
class PersonSlot(models.Model):
person = models.ForeignKey(Person)
timeslot = models.ForeignKey(Slot)
class Meta:
db_table = 'person_free_slots'
unique_together = (('timeslot', 'person'))
我们在即将到来的一年中每隔15分钟创建365 * 24 * 4个TimeSlot模型,如果人员在他的日程表中指示空闲时间,我们会添加与该TimeSlot的关系。 有了这种架构,人们获得免费的时间段就像通过经理:person.free_time_slots一样简单 让所有人在某个时间(例如10-10:45)获得自由也很容易,平滑如下:
timeslots = TimeSlot.objects.filter(time__in=['10:00', '10:15', '10:30'])
PersonSlot.objects.filter(timeslot__in=timeslots).values('person')
解决方案№2: 我们避免为每个时隙创建模型,但在PersonTime模型中保留日期:
class Person(models.Model):
name = models.CharField(max_length=32, null=False, blank=False)
class TimeSlot(models.Model):
person = models.ForeignKey(Person, related_name='slots')
time_start = models.DateTimeField(db_index=True)
time_end = models.DateTimeField(db_index=True)
获取空闲时间列表也很容易(person.slots)。让所有人在某个时间(例如10-10:45)获得自由就像是:
TimeSlot.objects.filter(time_start__gte="10:00", time_end__lte="10:45").values('person')
此解决方案不适用于交叉间隔,我不确定是否可以查询间隔的索引时间(在同一字段上使用gte和lte比较),并且可以快速工作。如果重要,我会使用Postgres。我还用伪代码编写了时间查询,以使代码更简单。
所以我的问题如下,django开发人员如何实现此功能以便为大数据上的两个查询提供速度?我希望就我目前的解决方案或新想法提出可能的警告/好处的建议。
答案 0 :(得分:3)
让我们将这个问题分成两部分。
考虑编码与时隙相关的数据。如果你需要15分钟的精度,你有96个插槽(1小时4个插槽,一天24小时),任何一天的持续时间为15分钟。每个插槽可以具有两种可能状态之一:1 - 插槽空闲,0 - 插槽忙(或者反之亦然,如果您愿意)。因此,您可以使用0
和1
字符串表示每日时间表。例如,字符串(仅为了便于阅读而添加的空格)0000 0000 0000 0000 0000 0000 0000 0000 0000 1110 0000 ...
表示上午00:00到09:00之间的繁忙时段(没有人在晚上工作),然后是上午9:00之间的空闲时段。上午9:45(连续三个1
),然后是从上午9:45开始的繁忙时段。
所以,你可以这样写你的模型:
class Person(models.Model):
name = models.CharField(max_length=32)
class DailySchedule(models.Model):
person = models.ForeignKey(Person, related_name='day_schedule')
date = models.DateField()
schedule = models.CharField(max_length=96)
因此,我们编码了有关可用/忙时隙的信息,但我们如何从数据库中提取它?幸运的是,Django有regex
字段查找的可能性!幸运的是,Django 1.4支持它!
因此,为了找到特定时段内的用户,您可以使用DailySchedule.objects.filter(date=date, schedule__regex=r'<expression>')
。由于用于提取不同时间范围的表达式并不明显,我们需要一个支持函数:
def time_slot_to_regex(start_time, end_time):
# times should be in HH:MM format
start_hour, start_minutes = start_time.split(':')
end_hour, end_minutes = end_time.split(':')
slots_before_needed_time = (int(start_hour)*4 + int(start_minutes)/15)
# compute how many hours are between given times and find out nr of slots
hour_duration_slots = (int(end_hour) - int(start_hour)) * 4 # 4 slots in each hour
# adjust nr of slots according to minutes in provided times.
# e.g. 9:30 to 10:45 - we have 10-9=1 hour, which is 4 time slots,
# but we need to subtract 2 time slots, because we don't have 9:00 to 10:00,
# but 9:30 to 10:00 so we subtract 30/15=2 timeslots and add what is left
# from the incomplete hour of 10:45 time, which is 45/15 minutes = 3 slots
minute_duration_slots = int(end_minutes)/15 - int(start_minutes)/15
total_duration = hour_duration_slots + minute_duration_slots
regular_expression = r'^[01]{%d}1{%d}' % (slots_before_needed_time, total_duration)
return regular_expression
让我们弄清楚这个功能是如何运作的
假设我们想要在9:15到9:45 AM之间找出可用的人。我们打电话给slots_expression = time_slot_to_regex('9:15', '9:45')
计算:
slots_before_needed_time = 37
,我们将9乘以4 + 15/15得到。这是我们不关心的广告位数量,这些广告位将包含在regular_expression
字符串的第一部分 - '^[01]{37}'
hour_duration_slots = 0
,因为两个时间值中的小时相同minute_duration_slots = 2
,我们从45/15中减去15/15 regular_expression
中设置为1,从而获得'^[01]{37}1{2}'
现在我们可以将这个正则表达式提供给我们的过滤器,获取DailySchedule.objects.filter(schedule__regex=slots_expression)
并瞧!,我们得到了结果。
我已经描述了编码数据的原理,但没有提到编码数据的过程。这可以通过使用另一个支持函数轻松完成,该函数采用一串现有繁忙/可用插槽以及start_date
和end_date
来更新现有计划。如果您还需要这种方法,请告诉我。
^[01]{36}1{4}[01]{24}1{4}
将查找从上午9点到上午10点和从下午4点到下午5点可用的人