2016-11-05 91 views
0

型号:如何将两个注释查询集合并成一个结果

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")) 

因为总和值multiplicates - 这是不幸的是预期的行为 - 因为JOINS

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

实施例:

酒吧1:

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

酒吧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 
+0

无法使用最新的Django 1.10.3 – madzohan

+0

@madzohan重现请马上检查更新后的代码 –

回答

1

你的问题是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 
+0

可以做两个查询,但我不确定这将如何工作。之后的结果与之前的结果相同。或者我错了? –

+1

对不起,该示例有几个错别字(现在已修复)。这个想法是手动在'result'中注释这些实例:当您再次迭代它时,您将从queryset缓存中获得相同的带注释的实例。你也可以只取得你需要的'values()'而不用in_bulk,并使用dict理解。 – emulbreh

+0

是的,它工作,我看着djangoproject的票,发现其他解决方案 - 现在我试图分析它。之后,我选择更好的答案; D –

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" 

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