2016-11-05 89 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" 

當然這並不完美 - 它需要一些錯誤檢查(例如雙引號)或別名,但我認爲這是正確的方向