2017-06-13 134 views
1

模型

這裏是我們的基本模型設置。基於ManyToMany關係過濾Django QuerySet

一個列表有很多項目,並且一個項目可以在許多列表中。對於給定的項目,如果其列表中的的任何好的(即,list.bad == False),那麼項目是好的。如果一個項目沒有出現在列表中,那麼它是不好

我們有一個自定義的項目查詢集,一個只返回好項目的方法和一個只返回壞項目的方法。

class Item(models.Model): 
    objects = ItemQuerySet.as_manager() 
    name = models.CharField(max_length=255, unique=True) 

class List(models.Model): 
    name = models.CharField(max_length=255, unique=True) 
    bad = models.BooleanField(default=True) 
    items = models.ManyToManyField(Item, related_name='lists') 

class ItemQuerySet(models.QuerySet): 
    def bad(self): 
     return self.exclude(lists__bad=False) 

    def good(self): 
     return self.filter(lists__bad=False) 

場景

下面是我們遇到麻煩的場景的例子:一個壞的名單,一個良好的名單,以及兩個項目。

BadList: GoodList: 
- Item1  - Item1 
- Item2 

由於項目1至少出現在一個良好的名單,就應該拿出在Item.objects.good(),而不是在Item.objects.bad()

由於Item2沒有出現在任何好的列表中,它應該出現在Item.objects.bad()中,而不是在Item.objects.good()中出現。

我們可以設置情景就像這樣:

# Create the two lists. 
>>> goodlist = List.objects.create(name='goodlist', bad=False) 
>>> badlist = List.objects.create(name='badlist', bad=True) 

# Create the two items. 
>>> item1 = Item.objects.create(name='item1') 
>>> item2 = Item.objects.create(name='item2') 

# Item1 goes in both lists 
>>> goodlist.items.add(item1) 
>>> badlist.items.add(item1) 

# Item2 only in badlist 
>>> badlist.items.add(item2) 

而且,事實上,Item.objects.good()Item.objects.bad()工作,因爲我們預計:

>>> Item.objects.bad() # This returns what we want! Good! 
<QuerySet [<Item: item2>]> 

>>> Item.objects.good() # This returns what we want! Good! 
<QuerySet [<Item: item1>]> 

的問題

感謝您與我的軸承。這是我們自定義的QuerySet出錯的地方。如果我們訪問good()bad()自定義的QuerySet方法單個List的Items,我們會得到不正確的結果。

>>> badlist.items.bad() # WRONG! We want to ONLY see item2 here! 
<QuerySet [<Item: item1>, <Item: item2>] 

>>> badlist.items.good() # WRONG! We want to see item1 here! 
<QuerySet []> 

好像,當我們做badlist.items.bad(),查詢只有考慮badlist在確定的項目是壞的,而不是考慮所有列出了貨品,但我很困惑,爲什麼情況就是這樣。

我的想法是,在ItemQuerySet.bad方法中,我想要的東西像self.exclude(any__lists__bad=False)而不是隻是self.exclude(lists__bad=False)。但當然any__關鍵字實際上並不存在,我不確定如何在Django QuerySet中正確表達該邏輯。看起來使用Q對象可能是前進的方向,但我仍然不確定如何用Q對象表示這樣的查詢。

在我們的實際數據庫中,少於100個列表,但數百萬個項目。因此,出於性能方面的原因,使用一個查詢而不是屬性或多個查詢來完成這項工作是理想的。

乾杯!

回答

1

如果打印出由badlist.items.bad()生成的查詢,您會看到問題:它將在通過表上使用WHERE子句,從而將列表限制爲僅限於壞列表。如果您想正確應用badgood,則需要從Item級別開始,然後按列表中的項目進行過濾。

item_ids = list(badlist.items.values_list('id'), flat=True) 

Item.objects.bad().filter(id__in=item_ids) 

Item.objects.good().filter(id__in=item_ids) 

編輯:沒有模式,我不能對此進行測試,但我認爲你可以使用標註來計數表的數,然後通過該

def annotate_good(self); 
    return self.annotate(good=Count(Case(When(lists__bad=False, then=1), default=0))) 

def good(self): 
    return self.annotate_good().exclude(good=0) 

def bad(self): 
    return self.annotate_good().filter(good=0) 

否則過濾,如果真的表現是一個問題,我會爲Item模型添加一個好的或不好的字段,並在保存時更新它,以便查詢變得非常簡單。

+0

感謝您的回答!這確實可以正常工作,但是我擔心其他使用該代碼的人會嘗試使用'badlist.items.good()'路由,但不知道更好。我想避免讓人們被這樣誤導。 此外,我打算在原始帖子中提到這一點:在我們的實際數據庫中,少於100個列表,但數百萬個項目。因此,出於性能方面的原因,使用一個查詢而不是屬性或多個查詢來完成這項工作是理想的。 –

+1

啊,知道了。我認爲你可以使用queryset註釋來做到這一點。我使用我認爲應該可以工作的解決方案編輯了我的答案。否則,爲了提高性能,我只需將不良/好列添加到Item並更新它,這使得查詢變得更簡單 – Brobin