【问题标题】:Count events types per day for different timezones using Django ORM使用 Django ORM 计算不同时区每天的事件类型
【发布时间】:2020-07-17 11:47:06
【问题描述】:

我们有一个表格,其中包含多个事件以及添加事件的时间。用于存储事件的默认时区是 UTC。例如:

class Events:
    type = models.CharField(max_length=45, null=False)
    date_added = models.DateTimeField(auto_now_add=True)

现在,我们想要获取两个日期之间不同事件类型的每日计数 - start_date 和 end_date。例如:对于 start_date = "2020-03-1" 和 end_date = "2020-03-31",输出应该是 -

[{
    "date" : "2020-03-1",
    "event1" : 200,
    "event2" : 606,
    "event3" : 595
},
{
    "date" : "2020-03-2",
    "event1" : 357,
    "event2" : 71,
    "event3" : 634
},
{
    "date" : "2020-03-3",
    "event1" : 106,
    "event2" : 943,
    "event3" : 315
},
{
    "date" : "2020-03-4",
    "event1" : 187,
    "event2" : 912,
    "event3" : 743
},
.
.
.
.
{
    "date" : "2020-03-31",
    "event1" : 879,
    "event2" : 292,
    "event3" : 438
}]

由于用户位于不同的时区(美国、欧洲、亚洲等),我们希望在计算事件之前根据用户转换时区。以 UTC 计数在用户的时区中每天都会有错误的计数。例如:创建于 3 月 3 日凌晨 1:30 IST 的事件将在 3 月 2 日晚上 8 点(UTC 时间)显示并相应计数。

如果我们使用 for 循环,它会变得非常昂贵。因此,我们希望使用 Django ORM 在 DB 级别进行操作。如果不可能完全依赖 Django ORM,我们希望使其尽可能高效。

我们能想到的最好的查询是:

 Events.objects.filter(
    pk = user_pk, date__range = (
        (end_date - time_delta).strftime("%Y-%m-%d"), 
        end_date.strftime("%Y-%m-%d")
        )
    ).extra({
        "date_added" : "date(date_added)"
    }).values(
        "date_added", 
        "type"
    ).annotate(
        models.Count("type")
    ) 

我们得到的结果如下:

<QuerySet [{'date_added': datetime.date(2020, 3, 6), 'type': 'event1', 'type__count': 30}, 
{'date_added': datetime.date(2020, 3, 6), 'type': 'event2', 'type__count': 189}, 
{'date_added': datetime.date(2020, 3, 6), 'type': 'event3', 'type__count': 1}, 
{'date_added': datetime.date(2020, 3, 6), 'type': 'event4', 'type__count': 3}, 
{'date_added': datetime.date(2020, 3, 9), 'type': 'event2', 'type__count': 57}, 
{'date_added': datetime.date(2020, 3, 9), 'type': 'event1', 'type__count': 23}, 
{'date_added': datetime.date(2020, 3, 9), 'type': 'event4', 'type__count': 1}, 
{'date_added': datetime.date(2020, 3, 10), 'type': 'event1', 'type__count': 5}, 
{'date_added': datetime.date(2020, 3, 10), 'type': 'event2', 'type__count': 21}, 
{'date_added': datetime.date(2020, 3, 11), 'type': 'event2', 'type__count': 9}, 
{'date_added': datetime.date(2020, 3, 11), 'type': 'event1', 'type__count': 15}, 
{'date_added': datetime.date(2020, 3, 12), 'type': 'event2', 'type__count': 49}, 
{'date_added': datetime.date(2020, 3, 13), 'type': 'event2', 'type__count': 8}, 
{'date_added': datetime.date(2020, 3, 13), 'type': 'event1', 'type__count': 3}, 
{'date_added': datetime.date(2020, 3, 17), 'type': 'event1', 'type__count': 16}, 
{'date_added': datetime.date(2020, 3, 17), 'type': 'event2', 'type__count': 26}, 
{'date_added': datetime.date(2020, 3, 17), 'type': 'event4', 'type__count': 1}, 
{'date_added': datetime.date(2020, 3, 17), 'type': 'event3', 'type__count': 1}, 
{'date_added': datetime.date(2020, 3, 18), 'type': 'event2', 'type__count': 64}, 
{'date_added': datetime.date(2020, 3, 18), 'type': 'event1', 'type__count': 11}, 
'...(remaining elements truncated)...']>

这仍然需要一个 for 循环来将具有相同日期的所有事件添加到一个字典中,但时区问题仍然存在。

如何解决?

【问题讨论】:

    标签: python django django-models mysql-python django-timezone


    【解决方案1】:

    我们终于解决了这个问题。我们仍在使用 for 循环来获取所需格式的数据,但我们能够将繁重的工作转移到数据库。先说几点:

    1. 我正在使用 MySQL 数据库。 (检查以下几点,了解为什么这是相关的。)

    2. 如果您想直接使用时差转换时区(IST 为“+05:30”,UTC 为“+00:00”),则无需运行任何命令。

    3. 但是,您需要运行命令以在 MySQL 中按名称支持时区 D B。例如:如果您要使用命名时区(“ASIA/KOLKATA”或“UTC”), 您将需要运行一个命令:
      • mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -D mysql -u root -p
    4. 此命令适用于在 Ubuntu 中运行的 MySQL DB。有类似的
      用于其他数据库(如 Postgresql)和平台(如 Windows)的命令。

    这是 Django ORM 查询:

    events_list = Events.objects.all().extra(
            {
                "date_added" : "date(CONVERT_TZ(date_added, 'UTC', 'America/Chicago'))"
            }
        ).values(
            "date_added",
            "type"
        ).annotate(
            models.Count(
                "type"
            )
        )
    

    这将给出以下格式的数据:

    <QuerySet [{'date_added': datetime.date(2020, 3, 6), 'type': 'event1', 'type__count': 31}, 
    {'date_added': datetime.date(2020, 3, 6), 'type': 'event2', 'type__count': 189}, 
    {'date_added': datetime.date(2020, 3, 6), 'type': 'event3', 'type__count': 1}, 
    {'date_added': datetime.date(2020, 3, 6), 'type': 'event4', 'type__count': 3}, 
    {'date_added': datetime.date(2020, 3, 9), 'type': 'event2', 'type__count': 58}, 
    {'date_added': datetime.date(2020, 3, 9), 'type': 'event1', 'type__count': 21}, 
    {'date_added': datetime.date(2020, 3, 9), 'type': 'event4', 'type__count': 1}, 
    {'date_added': datetime.date(2020, 3, 10), 'type': 'event1', 'type__count': 1}, 
    {'date_added': datetime.date(2020, 3, 10), 'type': 'event2', 'type__count': 23}, 
    {'date_added': datetime.date(2020, 3, 11), 'type': 'event2', 'type__count': 10}, 
    {'date_added': datetime.date(2020, 3, 11), 'type': 'event1', 'type__count': 16}, 
    {'date_added': datetime.date(2020, 3, 12), 'type': 'event2', 'type__count': 50}, 
    {'date_added': datetime.date(2020, 3, 13), 'type': 'event2', 'type__count': 10}, 
    {'date_added': datetime.date(2020, 3, 13), 'type': 'event1', 'type__count': 1}, 
    {'date_added': datetime.date(2020, 3, 17), 'type': 'event1', 'type__count': 19}, 
    {'date_added': datetime.date(2020, 3, 17), 'type': 'event2', 'type__count': 27}, 
    {'date_added': datetime.date(2020, 3, 17), 'type': 'event4', 'type__count': 3}, 
    {'date_added': datetime.date(2020, 3, 17), 'type': 'event3', 'type__count': 1}, 
    {'date_added': datetime.date(2020, 3, 18), 'type': 'event2', 'type__count': 61}, 
    {'date_added': datetime.date(2020, 3, 18), 'type': 'event1', 'type__count': 13}, 
    '...(remaining elements truncated)...']>
    

    这里特定事件的数量是在时区转换后计算的。现在,一旦我们在时区转换后获得了事件的计数,剩下的就是以所需的格式获取这些数据,这可以通过 for 循环轻松完成。

    PS:

    1. 如果查询有条件,可以使用filter() 而是全部。例如:

      from django.utils import timezone
      Events.objects.filter(
          type__in = ["event1", "event2"], 
          date__gt = (
              timezone.now() - timezone.timedelta(days =  30)
          )
      ).extra(
          {
              "date_added" : "date(CONVERT_TZ(date_added, 'UTC', 'ASIA/KOLKATA'))"
          }
      ).values(
          "date_added",
          "type"
      ).annotate(
          models.Count(
              "type"
          )
      ) 
      

      这将为事件类型 1 和 2 提供过去 30 天的数据。

    2. 您可以使用“+00:00”表示 UTC,“+05:30”表示亚洲/加尔各答。例如 : "date_added" : "date(CONVERT_TZ(date_added, '+00:00', '+05:30'))"

    【讨论】:

      猜你喜欢
      • 2019-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-20
      • 1970-01-01
      • 1970-01-01
      • 2021-12-10
      • 2020-03-26
      相关资源
      最近更新 更多