Django Query 每天将两行中的值聚合为单个结果

Posted

技术标签:

【中文标题】Django Query 每天将两行中的值聚合为单个结果【英文标题】:Django Query to aggregate values from two rows into single result per day 【发布时间】:2017-04-05 22:18:12 【问题描述】:

我正在尝试创建一个模型管理器查询,该查询返回给定日期范围内多种余额类型(DATA 和 AIRTIME)按天分组的结果。余额历史表会随着 SIM 卡使用数据而一直更新,但对于报告,我们只想每天显示一个余额

模型很简单:

class Sim(TimeStampedModel):
    number = models.CharField()

class SimBalanceHistory(TimeStampedModel):
    balance_type = models.CharField(choices=BALANCE_TYPES, max_length=10)
    amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    sim = models.ForeignKey(Sim, related_name='balance_histories')

SimBalanceHistory 表中的一些示例数据:

   ID   BALANCE_TYPE AMOUNT SIM_ID CREATED MODFIED
   1603 AIRTIME 3.71    348 2016-11-17 11:13:42.498180 +02:00   2016-11-17 11:13:43.543159 +02:00
   1604 DATA    36.75   348 2016-11-17 11:13:42.498180 +02:00   2016-11-17 11:13:43.543159 +02:00
   1703 AIRTIME 3.71    348 2016-11-17 11:13:42.498180 +02:00   2016-11-17 11:13:43.543159 +02:00
   1704 DATA    36.74   348 2016-11-17 11:13:42.498180 +02:00   2016-11-17 11:13:43.543159 +02:00
   1803 AIRTIME 3.71    348 2016-11-17 11:13:42.498180 +02:00   2016-11-17 11:13:43.543159 +02:00
   1804 DATA    36.73   348 2016-11-17 11:13:42.498180 +02:00   2016-11-17 11:13:43.543159 +02:00
   1973 AIRTIME 3.71    348 2016-11-17 11:13:42.498180 +02:00   2016-11-17 11:13:43.543159 +02:00
   1974 DATA    36.72   348 2016-11-17 11:13:42.498180 +02:00   2016-11-17 11:13:43.543159 +02:00
   2059 AIRTIME 3.71    348 2016-11-17 11:13:42.498180 +02:00   2016-11-17 11:13:43.543159 +02:00
   2060 DATA    36.72   348 2016-11-17 11:13:42.498180 +02:00   2016-11-17 11:13:43.543159 +02:00
   2135 AIRTIME 3.71    348 2016-11-17 11:13:42.498180 +02:00   2016-11-17 11:13:43.543159 +02:00
   2136 DATA    36.71   348 2016-11-17 11:13:42.498180 +02:00   2016-11-17 11:13:43.543159 +02:00
   2229 AIRTIME 3.71    348 2016-11-17 11:13:42.498180 +02:00   2016-11-17 11:13:43.543159 +02:00
   2230 DATA    36.70   348 2016-11-17 11:13:42.498180 +02:00   2016-11-17 11:13:43.543159 
   440026   DATA    34.26   348 2016-11-18 23:34:36.976777 +02:00   2016-11-18 23:34:36.976836 +02:00
   440885   AIRTIME 3.71    348 2016-11-18 23:57:57.448809 +02:00   2016-11-18 23:57:57.448878 +02:00
   440889   DATA    34.25   348 2016-11-18 23:57:58.854901 +02:00   2016-11-18 23:57:58.854959 +02:00
   443590   AIRTIME 3.71    348 2016-11-19 00:35:07.598679 +02:00   2016-11-19 00:35:07.598755 +02:00

443593 数据 34.24 348 2016-11-19 00:35:08.991217 +02:00 2016-11-19 00:35:08.991266

目前查询如下:

    def daily_balances(self, start_date, end_date):
      return self.filter(
        created__range=[start_date, end_date]
      ).dates(
        'created',
        'day',
        order='DESC'
      ).order_by(
        '-created'
      ).distinct(
        'created', 'balance_type'
      ).values(
        'created',
        'amount',
        'balance_type'
      )

按天限制,但为每个 balance_type 返回一行

'balance_type': 'AIRTIME', 'created': datetime.datetime(2016, 11, 22, 0, 0, tzinfo=<UTC>), 'amount': Decimal('5.00')
'balance_type': 'DATA', 'created': datetime.datetime(2016, 11, 22, 0, 0, tzinfo=<UTC>), 'amount': Decimal('12.00')

我试图在查询集的结果中得到类似这样的结果(每天 1 条记录,其值为通话时间量和数据量:

 'created': datetime.datetime(2016, 11, 22, 0, 0, tzinfo=<UTC>), 'data_amount': Decimal('5.00'), 'airtime_amount': Decimal('12.00')
 'created': datetime.datetime(2016, 11, 21, 0, 0, tzinfo=<UTC>), 'data_amount': Decimal('6.00'), 'airtime_amount': Decimal('14.00')

【问题讨论】:

【参考方案1】:

我认为您现有的查询已经很不错了,但是如果您真的希望每天有一行的余额,您可以使用conditional aggregates:

from django.db.models import IntegerField, F, Sum, When

SimBalanceHistory.objects\
                 .filter(created__range=[start_date, end_date])\
                 .dates('created', 'day', order='DESC')\
                 .values('created')\
                 .annotate(airtime_amount=Sum(Case(When(balance_type='AIRTIME', then=F('amount')), output_field=DecimalField())),
                           data_amount=Sum(Case(When(balance_type='DATA', then=F('amount')), output_field=DecimalField())))

【讨论】:

感谢这是史诗【参考方案2】:

这是我会尝试的。

首先,在每行中使用特定于其类型的名称重命名金额:data_amount 和 airtime_amount。 然后,在计算每个金额的总和时按“已创建”对行进行分组。

所以,我认为你可以做类似的事情

from django.db.models import F, Sum

def daily_balances(self, start_date, end_date):
    airtime_records = SimBalanceHistory.objects.filter(
        created__range=[start_date, end_date],     
        balance_type='AIRTIME'
    ).annotate(airtime_amount=F('amount'))

    data_records = SimBalanceHistory.objects.filter(
        created__range=[start_date, end_date],
        balance_type='DATA'
    ).annotate(data_amount=F('amount'))

    return (airtime_records | data_records).dates(
        'created',
        'day',
        order='DESC'
    ).order_by(
        '-created'
    ).values('created').annotate(
        Sum('data_amount'),
        Sum('airtime_amount'),
    )

该代码进行了多次查询,但我想不出一个解决方案只在一个中执行此操作。希望有人会发布更好的答案,但也许这可以帮助你。

参考。

How to query as GROUP BY in django? How to rename items in values() in Django? Howto merge 2 Django QuerySets in one and make a SELECT DISTINCT 聚合和注释: https://docs.djangoproject.com/en/1.10/topics/db/aggregation/

【讨论】:

感谢您的帮助,当我尝试合并两个查询集时,由于前两个查询中金额字段的名称不同,因此无法合并。

以上是关于Django Query 每天将两行中的值聚合为单个结果的主要内容,如果未能解决你的问题,请参考以下文章

从两行中获取每个值的差异

Spark窗口函数按行中最常见的值聚合

在 Django 中的 ValuesQuerySet 上使用 extra()

应用程序启动器文本 - 两行中的应用程序名称

Django自动将文本字段中的双引号转换为单引号

引导两行导航栏(顶行中的链接切换底行中的导航栏),行之间的按钮