如何将两个带注释的查询集合并为一个结果

时间:2016-11-05 11:15:16

标签: django

型号:

class Foo(models.model):
  name =  models.CharField(max_length = 50, blank = True, unique = True)

class Bar1(models.Model):
  foo = models.ForeignKey('Foo')
  value = models.DecimalField(max_digits=10,decimal_places=2)

class Bar2(models.Model):
  foo = models.ForeignKey('Foo')
  value = models.DecimalField(max_digits=10,decimal_places=2)

Clasess Bar1和Bar2是无关的,所以我不能把它作为一个类来解决问题。但这只是表明问题尽可能纯粹的例子。

first = Foo.objects.all().annotate(Sum("bar1__value"))
second = Foo.objects.all().annotate(Sum("bar2__value"))

每个查询集都包含正确的值。

我无法将其合并到:

 both = Foo.objects.all().annotate(Sum("bar1__value")).annotate(Sum("bar2__value"))

因为和值乘以 - 这是不幸的预期行为 - 因为JOINS

现在问题 - 如何合并/加入第一个和第二个以获得两者?

示例:

酒吧1:

  foo | value
--------------
   A  |  10
   B  |  20
   B  |  20

Bar 2:

  foo | value
--------------
   A  |  -0.10
   A  |  -0.10
   B  |  -0.25

两者(值的不同取决于输入bar1和bar2的顺序)

  foo | bar1__value__sum | bar2__value__sum
---------------------------------
   A  |  20              | -0.20
   B  |  40              | -0.50

预期结果:

  foo | bar1__value__sum | bar2__value__sum
---------------------------------
   A  |  10              | -0.20
   B  |  40              | -0.25

我无法使用itertools.chains,因为结果是:

  foo | bar1__value__sum | bar2__value__sum
---------------------------------
   A  |  null            | -0.20
   B  |  null            | -0.25
   A  |  10              | null
   B  |  40              | null

3 个答案:

答案 0 :(得分:2)

你的问题是Django ORM的一个已知限制:https://code.djangoproject.com/ticket/10060

如果你可以做两个查询,可以选择以下一个选项:

result = Foo.objects.annotate(b1_sum=Sum("bar1__value"))
bar2_sums = Foo.objects.annotate(b2_sum=Sum("bar2__value")).in_bulk()
for foo in result:
    foo.b2_sum = bar2_sums.get(foo.pk).b2_sum

答案 1 :(得分:0)

根据@emulbreh的回答我读了票并找到了一些解决方案。我走这条路做了这个:

models.py:

from django.db.models.expressions import RawSQL
from django.db.models.query import QuerySet
(...)
class NewManager(models.Manager):
  """A re-usable Manager to access a custom QuerySet"""
  def __getattr__(self, attr, *args):
    try:
      return getattr(self.__class__, attr, *args)
    except AttributeError:
    # don't delegate internal methods to the queryset
      if attr.startswith('__') and attr.endswith('__'):
        raise
      return getattr(self.get_query_set(), attr, *args)

  def get_query_set(self):
    return self.model.QuerySet(self.model, using=self._db)


class Foo(models.Model):
  name =  models.CharField(max_length = 50, blank = True, unique = True)
  objects =NewManager()
  def __str__(self):
    return self.name

  class QuerySet(QuerySet):
    def annotate_sum(self, modelClass, field_name):
      annotation_name="%s__%s__%s" % (modelClass._meta.model_name,field_name,'sum')
      raw_query = "SELECT SUM({field}) FROM {model2} WHERE {model2}.{model3}_id = {model1}.id".format(
              field = field_name,
              model3 = self.model._meta.model_name,
              model2 = modelClass._meta.db_table,
              model1 = self.model._meta.db_table
          )
      debug.debug("%s" % raw_query)
      annotation = {annotation_name: RawSQL(raw_query, [])}

      return self.annotate(**annotation)

和views.py:

both = Foo.objects.annotate_sum(Bar1, 'value').annotate_sum( Bar2, 'value')

sql结果完全符合我的要求:

SELECT "app_foo"."id", "app_foo"."name", (SELECT SUM(value) FROM app_bar1 WHERE app_bar1.foo_id = app_foo.id) AS "bar1__value__sum", (SELECT SUM(value) FROM app_bar2 WHERE app_bar2.foo_id = app_foo.id) AS "bar2__value__sum" FROM "app_foo"

当然它并不完美 - 它需要一些错误检查(例如双引号)或别名,但我认为这是正确的方向

答案 2 :(得分:0)

遇到类似问题后,我进入了此页面,但使用System.out.print(list.toString()); 而不是Count

最简单的解决方案是在第二个Sum上使用Count(<field>, distinct=True),即

Count

参考文献: