模型

这是我们拥有的基本模型设置。

列表有很多项目,一个项目可以在很多列表中。对于给定的项目,如果其任何列表良好(即list.bad == False),则该项目为良好。如果某项没有出现在任何好的列表中,则说明它是不好的。

我们有一个自定义的QuerySet for Items,有一个只返回好项目的方法和一个只返回坏项目的方法。

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的Items访问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个,但有数百万个项目。因此,出于性能原因,理想的情况是只执行一个查询,而不是一个属性或多个查询。

干杯!

最佳答案

如果打印出由badlist.items.bad()生成的查询,您将看到问题:它将在穿透表上使用WHERE子句,从而将列表限制为仅坏列表。如果要正确应用Itembad,则需要从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模型添加一个好坏字段,并在保存时对其进行更新,以使查询变得非常简单。

关于python - 基于ManyToMany关系存在过滤Django QuerySet,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44528734/

10-11 08:54