【发布时间】:2020-04-10 05:00:13
【问题描述】:
我有一个附加到 MySQL 数据库的 Django 应用程序。数据库中充满了记录 - 其中有几百万条记录。
我的模型如下所示:
class LAN(models.Model):
...
class Record(models.Model):
start_time = models.DateTimeField(...)
end_time = models.DateTimeField(...)
ip_address = models.CharField(...)
LAN = models.ForeignKey(LAN, related_name="records", ...)
bytes_downloaded = models.BigIntegerField(...)
bytes_uploaded = models.BigIntegerField(...)
每条记录都反映了一个时间窗口,并显示特定 LAN 上的特定 IP 地址是否在该窗口期间进行了任何下载或上传。
我需要知道的是: 给定一个开始日期和结束日期,给我一张表格,说明特定 LAN 有哪些 DAYS 活动(有任何记录)
例如:
在 1 月 1 日和 1 月 31 日之间,告诉我 LAN A 在哪一天有任何记录 他们
假设有一段时间,LAN 会一次关闭几天,并且在这些日子里没有任何记录或任何活动。
我的解决方案:
我可以通过将一些方法附加到我的 LAN 模型来以缓慢的方式做到这一点:
class LAN(models.Model):
...
# Returns True if there are records for the current LAN between 2 given dates
# Returns False otherwise
def online(self, start, end):
criterion1 = Q(start_time__lt=end)
criterion2 = Q(end_time__gt=start)
return self.records.filter(criterion1 & criterion2).exists()
# Returns a list of days that a LAN was online for between 2 given dates
def list_online_days(self, start, end):
start_date = timezone.make_aware(timezone.datetime.strptime(start, "%b %d, %Y"))
end_date = timezone.make_aware(timezone.datetime.strptime(end, "%b %d, %Y"))
end_date = end_date.replace(hour=23, minute=59, second=59, microsecond=999999)
days_online = []
current_date = start.astimezone()
while current_date <= end:
start_of_day = current_date.replace(hour=0, minute=0, second=0, microsecond=0)
end_of_day = current_date.replace(hour=23, minute=59, second=59, microsecond=999999)
if self.online(start=start_of_day, end=end_of_day):
days_online.append(current_date.date())
current_date += timezone.timedelta(days=1)
return days_online
此时,我可以运行:
lan = LAN.objects.get(id=1) # Or whatever LAN I'm interested in
days_online = lan.list_online_days(start="Jan 1, 2020", end="Jan 31, 2020")
这可行,但会导致在我的开始日期和结束日期之间每天运行一个查询。在这种情况下,有 31 个查询(1 月 1 日、1 月 2 日等)。 这使得它在很长一段时间内非常非常慢,因为它需要遍历数据库中的所有记录 31 次。数据库索引有帮助,但在数据库中有足够的数据时它仍然很慢。
有没有办法通过单个数据库查询来满足我的需求?
我觉得它看起来像这样,但我不能完全正确:
lan.records.filter(criterion1 & criterion2).annotate(date=TruncDay('start_time')).order_by('date').distinct().values('date').annotate(exists=Exists(SOMETHING))
第一部分:
lan.records.filter(criterion1 & criterion2).annotate(date=TruncDay('start_time')).order_by('date').distinct().values('date')
似乎给了我想要的东西 - 每天一个值,但我不确定如何使用存在字段来注释结果,该字段显示当天是否存在任何记录。
注意:这是我的应用程序的简化版本 - 不是确切的模型和字段,所以如果某些事情可以改进,比如不将 CharField 用于 ip_address 字段,请不要过分关注这一点
【问题讨论】:
标签: mysql django python-3.x