【问题标题】:Why does MYSQL DB return a corrupted value when averaging over a Django models.DateTimeField?为什么 MYSQL DB 在对 Django models.DateTimeField 进行平均时返回损坏的值?
【发布时间】:2015-09-06 22:08:55
【问题描述】:

我在 MySQL(实际上是 MariaDB)数据库上运行 Django 应用程序。

我的 Django 模型如下所示:

from django.db import models
from django.db.models import Avg, Max, Min, Count

class myModel(models.Model):
    my_string = models.CharField(max_length=32,)
    my_date = models.DateTimeField()

    @staticmethod
    def get_stats():            
        logger.info(myModel.objects.values('my_string').annotate(
                count=Count("my_string"), 
                min=Min('my_date'), 
                max=Max('my_date'), 
                avg=Avg('my_date'),
            )
        )

当我运行get_stats() 时,我得到以下日志行:

[2015-06-21 09:45:40] INFO [all_logs:96] [{'my_string': u'A', 'count': 2, 'avg': 20080507582679.5, 'min': datetime.datetime(2007, 8, 2, 11, 33, 53, tzinfo=<UTC>), 'max': datetime.datetime(2009, 2, 13, 5, 20, 6, tzinfo=<UTC>)}]

我遇到的问题是数据库返回的 my_date 字段的平均值是:20080507582679.5。仔细看看那个数字。这是无效的日期格式。

为什么数据库没有为这两个日期的平均值返回一个有效值?如果描述的方式失败,我如何获得该字段的实际平均值? Django DateTimeField 是否未设置为处理平均?

【问题讨论】:

    标签: python mysql django django-models mariadb


    【解决方案1】:

    Q1:为什么数据库没有返回这两个日期的平均值的有效值?

    答:返回的值是预期的,它是明确定义的 MySQL 行为。

    如果在数字上下文中使用日期或时间值,MySQL会自动将日期或时间值转换为数字,反之亦然。

    MySQL 参考手册:https://dev.mysql.com/doc/refman/5.5/en/date-and-time-types.html


    在 MySQL 中,AVG 聚合函数对 numeric 值进行操作。

    在 MySQL 中,DATEDATETIME 表达式可以在 numeric 上下文中求值。

    作为一个简单的演示,对DATETIME 执行numeric 加法运算会将日期时间值隐式转换为数字。这个查询:

      SELECT NOW(), NOW()+0
    

    返回如下结果:

      NOW()                                NOW()+0  
      -------------------  -----------------------
      2015-06-23 17:57:48    20150623175748.000000
    

    请注意,表达式 NOW()+0 的返回值不是DATETIME,它是一个数字

    当您在DATETIME 表达式上指定SUM()AVG() 函数时,这相当于将DATETIME 转换为数字,然后求和 或取平均值。

    也就是说,这个表达式AVG(mydatetimecol)的返回等价于这个表达式的返回:AVG(mydatetimecol+0)

    “平均”是一个数值。而且您已经观察到,返回的值不是有效的日期时间;即使在它恰好看起来像一个有效的日期时间的情况下,它也可能不是您认为真正的“平均值”的值。


    Q2:如果描述的方式失败,我如何获得该字段的实际平均值?

    A2:一种方法是将日期时间转换为可以“准确”平均的数值,然后将其转换回日期时间。

    例如,您可以将日期时间转换为表示从某个固定时间点开始的秒数的数值,例如

      TIMESTAMPDIFF(SECOND,'2015-01-01',t.my_date)
    

    然后您可以“平均”这些值,以从固定时间点获得平均秒数。 (注意:当心添加非常多的行,具有非常大的值,并超过限制(最大数值),数字溢出问题。)

      AVG(TIMESTAMPDIFF(SECOND,'2015-01-01',t.my_date))
    

    要将其转换回日期时间,请将该值作为秒数添加回固定时间点:

      '2015-01-01' + INTERVAL AVG(TIMESTAMPDIFF(SECOND,'2015-01-01',t.my_date)) SECOND
    

    (请注意,DATEIME 的值是在 MySQL 会话的时区中评估的;因此在某些极端情况下,MySQL 会话中的 time_zone 变量的设置会对返回的值产生一些影响。)

    MySQL 还提供了一个 UNIX_TIMESTAMP() 函数,它返回一个 unix 样式的整数值,即从纪元开始(UTC 时间 1970 年 1 月 1 日午夜)开始的秒数。您可以使用它来更简洁地完成相同的操作:

      FROM_UNIXTIME(AVG(UNIX_TIMESTAMP(t.my_date)))
    

    请注意,这个最终表达式实际上是在做同样的事情...将日期时间值转换为自 '1970-01-01 00:00:00' UTC 以来的秒数,取其平均值,然后将该平均秒数添加回 '1970-01-01' UTC,最后将其转换回 DATETIME 值,在当前会话 time_zone 中表示。


    Q3:Django DateTimeField 是否未设置为处理平均?

    答:显然,Django 的作者对从数据库返回的 SQL 表达式 AVG(datetime) 的值感到满意。

    【讨论】:

    • 谢谢。这太糟糕了。这似乎是一个错误。
    • @SaqibAli:如果没有记录在案的行为,它可能被视为 MySQL 中的错误。但是由于该行为在参考手册中进行了描述和记录,因此它不被视为错误。 (如果您尝试执行AVG(datetime),其他数据库将抛出错误,Django 将返回该错误。)
    • 如何编写 Django 以获取以下表达式中 UNIX_TIMESTAMP(t.my_date) 的总和? myModel.objects.values('my_string').annotate(count=Count("my_string"), min=Min('my_date'), max=Max('my_date'), sum=Sum(WHAT_ GOES_HERE),) 一旦我知道了,我可以很容易地算出平均值。
    【解决方案2】:

    方案 A:使用 TIMESTAMP 字段而不是 DATETIME 字段

    方案 B:在计算过程中将 DATETIME 转换为 TIMESTAMP:

    FROM_UNIXTIME(ROUND(AVG(UNIX_TIMESTAMP(`my_date`))))
    

    (抱歉,我不知道所需的 Django 语法。)

    【讨论】:

      【解决方案3】:

      当你使用values() 时,Django 不会转换它从 database-python 连接器获得的值。由连接器决定如何返回值。

      在这种情况下,MySQL 连接器似乎返回了一个字符串表示,其中删除了分隔符。您可以尝试将datetime.strptime() 与匹配的format 一起使用,将其解析为datetime 对象。

      【讨论】:

      • 除了它不是时间戳 - 它太大了八个数量级。实际上它似乎是一个删除了所有分隔符的日期字符串。
      • .....并删除了一些前导/填充零,因此您无法分辨所有这些边界在哪里。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-01-30
      • 1970-01-01
      • 2015-05-31
      • 2012-01-11
      • 1970-01-01
      • 1970-01-01
      • 2011-01-27
      相关资源
      最近更新 更多