Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/331.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python Django与MySQL后端-按时间范围分组_Python_Mysql_Django_Django Models - Fatal编程技术网

Python Django与MySQL后端-按时间范围分组

Python Django与MySQL后端-按时间范围分组,python,mysql,django,django-models,Python,Mysql,Django,Django Models,我有一个简单的模型: 型号.py class Ping(models.Model): online = models.BooleanField() created = models.DateTimeField(db_index=True, default=timezone.now) def __str__(self): return f'{self.online}, {self.created}' class PingManager(models.Ma

我有一个简单的模型:

型号.py

class Ping(models.Model):
    online = models.BooleanField()
    created = models.DateTimeField(db_index=True, default=timezone.now)

    def __str__(self):
        return f'{self.online}, {self.created}'
class PingManager(models.Manager):
    def downtime_python(self):
        queryset = super().get_queryset().filter(created__gt=timezone.now() - timezone.timedelta(days=30))
        offline = False
        ret = []
        for entry in queryset:
            if not entry.online and not offline:
                offline = True
                _ret = {'start': str(entry.created)}
            if entry.online and offline:
                _ret.update({'end': str(entry.created)})
                ret.append(_ret)
                offline = False
        return ret

    def downtime_sql(self):
        queryset = super().get_queryset().filter(created__gt=timezone.now() - timezone.timedelta(days=30))
        offline = queryset.filter(online=False).order_by('created').first()
        last = queryset.order_by('created').last()
        ret = []
        if offline:
            online = queryset.filter(created__gt=offline.created, online=True).order_by('created').first()
            ret.append({'start': str(offline.created), 'end': str(online.created)})
            while True:
                offline = queryset.filter(created__gt=online.created, online=False).order_by('created').first()
                if offline:
                    online = queryset.filter(created__gt=offline.created, online=True).order_by('created').first()
                if (online and offline) and online.created < last.created:
                    ret.append({'start': str(offline.created), 'end': str(online.created)})
                    continue
                else:
                    break
        return ret

class Ping(models.Model):
    online = models.BooleanField()
    created = models.DateTimeField(db_index=True, default=timezone.now)
    objects = PingManager()

    def __str__(self):
        return f'{self.online}, {self.created}'
它给了我以下结果:

mysql [lab]> SELECT * FROM myapp_ping;
+----+--------+----------------------------+
| id | online | created                    |
+----+--------+----------------------------+
|  1 |      1 | 2018-08-02 13:34:09.435292 |
|  2 |      1 | 2018-08-02 13:35:09.520200 |
|  3 |      0 | 2018-08-02 13:36:09.540638 |
|  4 |      0 | 2018-08-02 13:37:10.529783 |
|  5 |      1 | 2018-08-02 13:38:09.779012 |
|  6 |      1 | 2018-08-02 13:39:09.650365 |
|  7 |      1 | 2018-08-02 13:40:09.625543 |
|  8 |      1 | 2018-08-02 13:41:09.892196 |
|  9 |      1 | 2018-08-02 13:42:09.802186 |
| 10 |      1 | 2018-08-02 13:43:09.864551 |
| 11 |      1 | 2018-08-02 13:44:09.960962 |
| 12 |      1 | 2018-08-02 13:45:09.891947 |
| 13 |      0 | 2018-08-02 13:46:09.141727 |
| 14 |      0 | 2018-08-02 13:47:09.142030 |
| 15 |      0 | 2018-08-02 13:48:09.160942 |
| 16 |      0 | 2018-08-02 13:49:09.152879 |
| 17 |      0 | 2018-08-02 13:50:09.280246 |
| 18 |      1 | 2018-08-02 13:51:09.363184 |
| 19 |      1 | 2018-08-02 13:52:09.405863 |
| 20 |      1 | 2018-08-02 13:53:09.403251 |
+----+--------+----------------------------+
20 rows in set (0.00 sec)
是否有一种方法可以获得类似的输出(在
online
为false的范围内):

停机时间:

from                | to                  | duration
2018-08-02 13:36:09 | 2018-08-02 13:37:10 | 1 minute and 1 second
2018-08-02 13:46:09 | 2018-08-02 13:50:09 | 4 minutes and 0 seconds
# python manage.py shell
Python 3.6.5 (default, Apr 10 2018, 17:08:37) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from myapp.models import Ping

In [2]: Ping.objects.downtime_sql()[0]
Out[2]: 
{'start': '2018-07-13 16:32:16.009356+00:00',
 'end': '2018-07-13 16:33:15.942784+00:00'}

In [3]: Ping.objects.downtime_python()[0]
Out[3]: 
{'start': '2018-07-13 16:32:16.009356+00:00',
 'end': '2018-07-13 16:33:15.942784+00:00'}

In [4]: Ping.objects.downtime_sql() == Ping.objects.downtime_python()
Out[4]: True

In [5]: import timeit

In [6]: timeit.timeit(stmt=Ping.objects.downtime_python, number=1)
Out[6]: 5.720254830084741

In [7]: timeit.timeit(stmt=Ping.objects.downtime_sql, number=1)
Out[7]: 0.25946347787976265
我不确定这是否可以用Django ORM实现,或者它需要一个原始的MySQL查询来使用
CASE
if
语句

更新:2018年8月8日星期三15:13:15 UTC

因此,我从以下方面获得了两种解决方案的概念证明:

型号.py

class Ping(models.Model):
    online = models.BooleanField()
    created = models.DateTimeField(db_index=True, default=timezone.now)

    def __str__(self):
        return f'{self.online}, {self.created}'
class PingManager(models.Manager):
    def downtime_python(self):
        queryset = super().get_queryset().filter(created__gt=timezone.now() - timezone.timedelta(days=30))
        offline = False
        ret = []
        for entry in queryset:
            if not entry.online and not offline:
                offline = True
                _ret = {'start': str(entry.created)}
            if entry.online and offline:
                _ret.update({'end': str(entry.created)})
                ret.append(_ret)
                offline = False
        return ret

    def downtime_sql(self):
        queryset = super().get_queryset().filter(created__gt=timezone.now() - timezone.timedelta(days=30))
        offline = queryset.filter(online=False).order_by('created').first()
        last = queryset.order_by('created').last()
        ret = []
        if offline:
            online = queryset.filter(created__gt=offline.created, online=True).order_by('created').first()
            ret.append({'start': str(offline.created), 'end': str(online.created)})
            while True:
                offline = queryset.filter(created__gt=online.created, online=False).order_by('created').first()
                if offline:
                    online = queryset.filter(created__gt=offline.created, online=True).order_by('created').first()
                if (online and offline) and online.created < last.created:
                    ret.append({'start': str(offline.created), 'end': str(online.created)})
                    continue
                else:
                    break
        return ret

class Ping(models.Model):
    online = models.BooleanField()
    created = models.DateTimeField(db_index=True, default=timezone.now)
    objects = PingManager()

    def __str__(self):
        return f'{self.online}, {self.created}'

关于我的评论:


我不确定SQL case/if语句是否能得到这个结果,因为结果行依赖于前面的行。不过,这在Python中是很容易按程序完成的

  • 最明显的方法是循环使用
    Ping.objects.all()
    (或
    Ping.objects.iterator()
    )并跟踪
    在线变量以形成所需的“条纹”。这样做的缺点是,您确实需要在每个对象上循环,这最终会很慢(和/或耗尽您的内存)
  • 一种更复杂的方法,使用更多的查询,但内存要少得多,就是找到第一个脱机的
    Ping
    对象,然后找到下一个(按时间)再次联机的
    Ping
    对象——这将形成一条条纹。然后冲洗并重复此操作,直到要检查的
    Ping
    对象用完为止
  • 编辑 是的,这里有一个方法2的具体实现(相当优雅,如果你不介意我说的话)(在下面找到完整的测试报告):

    它是一个2元组的生成器:
    ((开始时间戳,开始在线),(结束时间戳,结束在线)|无)

    例如,要获取过去10天内的上/下或下/上对

    for start, end in Ping.objects.filter(created__gt=now() - timedelta(days=10)).streaks():
        print(start, end)
    
    将打印类似于

    [...snip...]
    
    (datetime.datetime(2018, 8, 8, 8, 10, 12, 943500), False) (datetime.datetime(2018, 8, 8, 10, 10, 12, 943500), True)
    (datetime.datetime(2018, 8, 8, 10, 10, 12, 943500), True) (datetime.datetime(2018, 8, 8, 11, 10, 12, 943500), False)
    (datetime.datetime(2018, 8, 8, 11, 10, 12, 943500), False) (datetime.datetime(2018, 8, 8, 11, 40, 12, 943500), True)
    (datetime.datetime(2018, 8, 8, 11, 40, 12, 943500), True) (datetime.datetime(2018, 8, 8, 12, 40, 12, 943500), False)
    (datetime.datetime(2018, 8, 8, 12, 40, 12, 943500), False) (datetime.datetime(2018, 8, 8, 16, 40, 12, 943500), True)
    (datetime.datetime(2018, 8, 8, 16, 40, 12, 943500), True) (datetime.datetime(2018, 8, 8, 17, 40, 12, 943500), False)
    (datetime.datetime(2018, 8, 8, 17, 40, 12, 943500), False) (datetime.datetime(2018, 8, 8, 18, 10, 12, 943500), True)
    (datetime.datetime(2018, 8, 8, 18, 10, 12, 943500), True) (datetime.datetime(2018, 8, 8, 19, 40, 12, 943500), False)
    (datetime.datetime(2018, 8, 8, 19, 40, 12, 943500), False) (datetime.datetime(2018, 8, 8, 23, 10, 12, 943500), True)
    (datetime.datetime(2018, 8, 8, 23, 10, 12, 943500), True) (datetime.datetime(2018, 8, 9, 0, 10, 12, 943500), False)
    (datetime.datetime(2018, 8, 9, 0, 10, 12, 943500), False) (datetime.datetime(2018, 8, 9, 3, 10, 12, 943500), True)
    (datetime.datetime(2018, 8, 9, 3, 10, 12, 943500), True) (datetime.datetime(2018, 8, 9, 3, 40, 12, 943500), False)
    (datetime.datetime(2018, 8, 9, 3, 40, 12, 943500), False) (datetime.datetime(2018, 8, 9, 5, 10, 12, 943500), True)
    (datetime.datetime(2018, 8, 9, 5, 10, 12, 943500), True) (datetime.datetime(2018, 8, 9, 5, 40, 12, 943500), False)
    (datetime.datetime(2018, 8, 9, 5, 40, 12, 943500), False) (datetime.datetime(2018, 8, 9, 7, 10, 12, 943500), True)
    (datetime.datetime(2018, 8, 9, 7, 10, 12, 943500), True) None
    
    一些注意事项:

    • 最后一个
      结束
      值可能是
      ,这意味着机器仍处于上升或下降状态(取决于
      开始
      元组的状态值)
    • 如果您只关心机器停机的时间,只需忽略
      start
      元组状态值为
      True
      的对
    • 因为这是一个生成器,所以当您有足够的数据时,您可以停止对它的迭代,并且它不会进一步查询
    • 由于这是一个
      QuerySet
      扩展方法,您可以随意添加其他筛选器(只要它们不在
      联机
      上进行筛选)。例如,如果您有一个
      host
      字段,
      Ping.objects.filter(host='example.com').streaks()

    您可以使用
    @classmethod
    然后按照您想要的方式格式化输出,这里有一个示例:

    from dateutil.relativedelta import relativedelta
    
    
    class Ping(models.Model):
        online = models.BooleanField()
        created = models.DateTimeField(db_index=True, default=timezone.now)
    
        def __str__(self):
            return f'{self.online}, {self.created}'
    
        @classmethod
        def ping_online_duration(cls, is_online):
            first = cls.objects.filter(online=is_online).order_by('created').first()
            last = cls.objects.filter(online=is_online).order_by('created').last()
            return {
                'from': first.created.strftime('%Y-%m-%d %H:%M:%S'),
                'to': last.created.strftime('%Y-%m-%d %H:%M:%S'),
                'duration': (f'{relativedelta(last.created, first.created).minutes} minutes '
                             f'{relativedelta(last.created, first.created).seconds} seconds.')
            }
    
    你可以这样称呼它:

    对于在线组:

    Ping.ping_online_duration(True)
    
    {'from': '2018-08-02 15:02:19',
     'to': '2018-08-02 15:03:02',
     'duration': '0 minutes 43 seconds'}
    
    Ping.ping_online_duration(False)
    
    {'from': '2018-08-02 15:02:27',
     'to': '2018-08-02 15:03:01',
     'duration': '0 minutes 34 seconds'}
    
    对于离线组:

    Ping.ping_online_duration(True)
    
    {'from': '2018-08-02 15:02:19',
     'to': '2018-08-02 15:03:02',
     'duration': '0 minutes 43 seconds'}
    
    Ping.ping_online_duration(False)
    
    {'from': '2018-08-02 15:02:27',
     'to': '2018-08-02 15:03:01',
     'duration': '0 minutes 34 seconds'}
    

    正如我前面所说,您可以按需要格式化输出。

    我不确定SQL case/if语句是否可以得到该结果,因为结果行依赖于前面的行。这在Python中很容易实现。在这些方法中,
    queryset
    有多大<代码>停机时间\u python()
    将需要从数据库中加载所有数据,并将它们“反序列化”到模型中。它非常大,每分钟运行一次探测,大约30天,即40256天。这就是为什么它要慢得多的原因我喜欢这个概念,但结果是错误的。它将搜索联机为false的第一条和最后一条记录:
    >>relativedelta(last.created,first.created)relativedelta(days=+20,hours=+18,seconds=+59,microsonds=+988703)
    ->
    '0分59秒。
    。顺便说一句,在这种情况下,您为什么使用
    classmethod
    方法而不是
    staticmethod
    ?您是否有可能为这两种解决方案提供一些示例?请参阅我从
    Wed Aug 8 15:13:15 UTC 2018
    的更新,非常感谢您的评论。@HTF好的,我已经添加了我自己的实现:)