以下是我们的基本模型设置。
列表包含许多项目,而项目可以包含在多个列表中。对于给定的项目,如果其列表中的任何 良好(即list.bad == False
),则该项目良好。如果商品未出现在任何良好列表中,则错误。
我们有一个自定义的项目QuerySet,一个只返回好项目的方法,以及一个只返回坏项目的方法。
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
由于Item1出现在至少一个好的列表中,因此它应该出现在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出错的地方。如果我们通过单个List的项目访问good()
和bad()
自定义QuerySet方法,我们会得到错误的结果。
>>> 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个,但数百万个项目。因此,出于性能原因,最好使用一个查询,而不是属性或多个查询。
干杯!
答案 0 :(得分:1)
如果您打印出由badlist.items.bad()
生成的查询,您将看到问题:它将在直通表中使用WHERE
子句,从而将列表限制为仅列表。如果您想正确应用Item
和bad
,则需要从good
级别开始,然后按列表中的项目进行过滤。
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模型中添加一个好的或坏的字段,并在保存时更新它,以便查询变得简单。