【问题标题】:How can I pull data from my database using the Django ORM that annotates values for each day?如何使用 Django ORM 从我的数据库中提取数据,该 ORM 为每天的值添加注释?
【发布时间】: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


    【解决方案1】:

    答案最终比我想象的要简单,主要是因为我已经有了它。

    这个:

    lan.records.filter(criterion1 & criterion2).annotate(date=TruncDay('start_time')).order_by('date').distinct().values('date').annotate(exists=Exists(Record.objects.filter(pk=OuterRef('pk'))))
    

    是我所期待的,但它所做的只是在所有返回的日子里返回 exists=True,这是准确的,但并没有太大帮助。这是因为任何没有记录的日期已经从结果中省略了。

    这意味着我可以跳过整个注释部分,然后这样做:

    lan.records.filter(criterion1 & criterion2).annotate(date=TruncDay('start_time')).order_by('date').distinct().values('date')
    

    当有记录存在时,它已经给了我一个日期时间对象的列表,并跳过没有记录的任何地方。

    【讨论】:

      猜你喜欢
      • 2019-04-10
      • 2020-08-19
      • 2021-04-13
      • 2019-04-18
      • 2014-08-08
      • 2021-08-05
      • 2011-08-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多