Django在算术运算后聚合多个列

时间:2012-12-17 16:25:06

标签: django django-models

我对Django 1.4.4有一个非常奇怪的问题。

我有这个型号:

class LogQuarter(models.Model):
  timestamp = models.DateTimeField()
  domain = models.CharField(max_length=253)
  attempts = models.IntegerField()
  success = models.IntegerField()
  queue = models.IntegerField()
  ...

我需要收集具有较高sent属性的前20个域。发送的属性是尝试 - 队列。

这是我的要求:

obj = LogQuarter.objects\
      .aggregate(Sum(F('attempts')-F('queue')))\
      .values('domain')\
      .filter(**kwargs)\
      .order_by('-sent')[:20]

我也尝试了额外的功能,但它无法正常工作。

这是非常基本的SQL,我很惊讶Django不能这样做。

有人有解决方案吗?

2 个答案:

答案 0 :(得分:4)

您实际上可以通过子类化一些聚合功能来实现此目的。这需要深入挖掘代码才能真正理解,但这就是我为MAXMIN编写类似内容的代码。 (注意:此代码基于Django 1.4 / MySQL)。

首先继承基础聚合类并重写as_sql方法。此方法将实际SQL写入数据库查询。我们必须确保引用正确传递的字段并将其与正确的表名相关联。

from django.db.models.sql import aggregates
class SqlCalculatedSum(aggregates.Aggregate):
  sql_function = 'SUM'
  sql_template = '%(function)s(%(field)s - %(other_field)s)'

  def as_sql(self, qn, connection):
    # self.col is currently a tuple, where the first item is the table name and
    # the second item is the primary column name. Assuming our calculation is
    # on two fields in the same table, we can use that to our advantage. qn is
    # underlying DB quoting object and quotes things appropriately. The column
    # entry in the self.extra var is the actual database column name for the
    # secondary column.
    self.extra['other_field'] = '.'.join(
        [qn(c) for c in (self.col[0], self.extra['column'])])
    return super(SqlCalculatedSum, self).as_sql(qn, connection)

接下来,继承通用模型聚合类并覆盖add_to_query方法。此方法决定了聚合如何添加到基础查询对象。我们希望能够传入字段名称(例如queue),但获取相应的DB列名称(如果它不同的话)。

from django.db import models
class CalculatedSum(models.Aggregate):
  name = SqlCalculatedSum

  def add_to_query(self, query, alias, col, source, is_summary):
    # Utilize the fact that self.extra is set to all of the extra kwargs passed
    # in on initialization. We want to get the corresponding database column
    # name for whatever field we pass in to the "variable" kwarg.
    self.extra['column'] = query.model._meta.get_field(
        self.extra['variable']).db_column
    query.aggregates[alias] = self.name(
        col, source=source, is_summary=is_summary, **self.extra)

然后,您可以在这样的注释中使用新类:

queryset.annotate(calc_attempts=CalculatedSum('attempts', variable='queue'))

假设您的attemptsqueue字段具有相同的db列名,则应生成类似于以下内容的SQL:

SELECT SUM(`LogQuarter`.`attempts` - `LogQuarter`.`queue`) AS calc_attempts

然后你去。

答案 1 :(得分:0)

我不确定你是否可以这样做Sum(F('attempts')-F('queue'))。它应该首先抛出一个错误。我想,更简单的方法就是使用额外的。

result = LogQuarter.objects.extra(select={'sent':'(attempts-queue)'}, order_by=['-sent'])[:20]