jjzjj

python - 带有 MySQL 后端的 Django - 按时间范围分组

coder 2023-10-09 原文

我有这个简单的模型:

模型.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}'

它给了我以下结果:

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

我不确定这是否可以通过 Django ORM 完成,或者它是否需要原始 MySQL 查询来使用类似 CASEIF 语句?

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

所以我从 @AKX answer 获得了两种解决方案的概念证明:

模型.py

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}'

问题:

  1. 我应该为此创建静态方法还是自定义 manger 是正确的解决方案?

  2. 如果两个计算都在内存中运行,为什么执行时间会有如此巨大的差异?有没有办法改进并使其更像 Python 等效方法?

测试:

# 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

最佳答案

扩展我的评论:

I'm not sure even SQL case/if statements can get you that result, since the result rows depend on previous rows. This is easy to do procedurally in Python though.

  1. 最明显的方法是循环遍历 Ping.objects.all()(或 Ping.objects.iterator())并跟踪 online 变量来形成你想要的“条纹”。这样做的缺点是您确实需要遍历每个对象,这最终会很慢(和/或耗尽您的内存)。
  2. 一种更复杂的方法,它使用更多的查询但更少的内存,是找到第一个离线的 Ping 对象,然后找到下一个(按时间)Ping 对象再次联机——这将形成一个连胜。然后冲洗并重复此操作,直到用完要检查的 Ping 对象。

编辑

是的,这是方法 2 的具体实现(相当优雅,如果你不介意我这么说的话)(在 https://github.com/akx/so51656477 找到完整的测试库):

class PingQuerySet(models.QuerySet):
    def streaks(self):
        queryset = self.values_list('created', 'online').order_by('created')
        entry = queryset.first()
        while entry:
            next_entry = queryset.filter(created__gt=entry[0], online=(not entry[1])).first()
            yield (entry, next_entry)
            entry = next_entry

它是元组的二元组生成器:((start_timestamp, start_online), (end_timestamp, end_online) | None)

例如,要获取最近 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

一些注意事项:

  • 最后的end值可能是None,这意味着机器仍然是up或down(取决于start的status值> 元组)。
  • 如果您只关心机器停机的时间,只需忽略 start 元组的状态值为 True 的对。
  • 由于这是一个生成器,当您有足够的数据时,您可以停止迭代它,它不会再进行任何查询。
  • 由于这是一个 QuerySet 扩展方法,您可以根据需要添加其他过滤器(只要它们不在 online 上过滤)。例如,如果您有一个 host 字段,Ping.objects.filter(host='example.com').streaks()

关于python - 带有 MySQL 后端的 Django - 按时间范围分组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51656477/

有关python - 带有 MySQL 后端的 Django - 按时间范围分组的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  3. ruby - 触发器 ruby​​ 中 3 点范围运算符和 2 点范围运算符的区别 - 2

    请帮助我理解范围运算符...和..之间的区别,作为Ruby中使用的“触发器”。这是PragmaticProgrammersguidetoRuby中的一个示例:a=(11..20).collect{|i|(i%4==0)..(i%3==0)?i:nil}返回:[nil,12,nil,nil,nil,16,17,18,nil,20]还有:a=(11..20).collect{|i|(i%4==0)...(i%3==0)?i:nil}返回:[nil,12,13,14,15,16,17,18,nil,20] 最佳答案 触发器(又名f/f)是

  4. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

    我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

  5. ruby-on-rails - Ruby 检查日期时间是否为 iso8601 并保存 - 2

    我需要检查DateTime是否采用有效的ISO8601格式。喜欢:#iso8601?我检查了ruby​​是否有特定方法,但没有找到。目前我正在使用date.iso8601==date来检查这个。有什么好的方法吗?编辑解释我的环境,并改变问题的范围。因此,我的项目将使用jsapiFullCalendar,这就是我需要iso8601字符串格式的原因。我想知道更好或正确的方法是什么,以正确的格式将日期保存在数据库中,或者让ActiveRecord完成它们的工作并在我需要时间信息时对其进行操作。 最佳答案 我不太明白你的问题。我假设您想检查

  6. ruby - 当使用::指定模块时,为什么 Ruby 不在更高范围内查找类? - 2

    我刚刚被困在这个问题上一段时间了。以这个基地为例:moduleTopclassTestendmoduleFooendend稍后,我可以通过这样做在Foo中定义扩展Test的类:moduleTopmoduleFooclassSomeTest但是,如果我尝试通过使用::指定模块来最小化缩进:moduleTop::FooclassFailure这失败了:NameError:uninitializedconstantTop::Foo::Test这是一个错误,还是仅仅是Ruby解析变量名的方式的逻辑结果? 最佳答案 Isthisabug,or

  7. ruby-on-rails - 将 Ruby 中的日期/时间格式化为 YYYY-MM-DD HH :MM:SS - 2

    这个问题在这里已经有了答案:Railsformattingdate(4个答案)关闭4年前。我想格式化Time.Now函数以显示YYYY-MM-DDHH:MM:SS而不是:“2018-03-0909:47:19+0000”该函数需要放在时间中.现在功能。require‘roo’require‘roo-xls’require‘byebug’file_name=ARGV.first||“Template.xlsx”excel_file=Roo::Spreadsheet.open(“./#{file_name}“,extension::xlsx)xml=Nokogiri::XML::Build

  8. ruby - 查找字符串中的内容类型(数字、日期、时间、字符串等) - 2

    我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s

  9. Ruby 从大范围中获取第 n 个项目 - 2

    假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

  10. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

随机推荐