根据特定的许多关系过滤Django查询集

时间:2019-09-24 17:40:40

标签: django many-to-many django-queryset

我的模型类似于Django文档的Pizza示例:

class Pizza(models.Model):
    name = models.CharField()
    toppings = models.ManyToManyField('Topping')

    def __str__(self):
        return self.name


class Topping(models.Model):
    name = models.CharField()

    def __str__(self):
        return self.name

以及一些披萨的浇头:

>>> pepperoni = Topping.objects.create(name='pepperoni')
>>> sausage = Topping.objects.create(name='sausage')
>>> pineapple = Topping.objects.create(name='pineapple')
>>> olives = Topping.objects.create(name='olives')
>>> p1 = Pizza.objects.create(name='Pepperoni')
>>> p1.toppings.add(pepperoni)
>>> p2 = Pizza.objects.create(name='Sausage')
>>> p2.toppings.add(sausage)
>>> p3 = Pizza.objects.create(name='Pepperoni and Sausage')
>>> p3.toppings.add(pepperoni)
>>> p3.toppings.add(sausage)
>>> p4 = Pizza.objects.create(name='Pepperoni and Olives')
>>> p4.toppings.add(pepperoni)
>>> p4.toppings.add(olives)
>>> p5 = Pizza.objects.create(name='Pepperoni and Sausage and Olives')
>>> p5.toppings.add(pepperoni)
>>> p5.toppings.add(sausage)
>>> p5.toppings.add(olives)
>>> ...

我如何创建一个查询,该查询将仅返回带有意大利辣香肠(p1)或香肠(p2)或意大利辣香肠或香肠(p3的披萨)?我不想要包含意大利辣香肠,香肠和其他东西(p5)的比萨。

类似这样的东西将包括一个比萨饼,其中包含意大利辣香肠和橄榄(p4),我不想要:

>>> Pizza.objects.filter(toppings__in=[pepperoni, sausage])

我可以创建除我想要的两个浇头之外的所有浇头的列表,并将其用作排除项:

>>> toppings_i_do_not_want = Topping.objects.exclude(name__in=['Pepperoni', ['Sausage'])
>>> toppings_i_want = Topping.objects.filter(name__in=['Pepperoni', ['Sausage'])
>>> Pizza.objects.filter(toppings__in=toppings_i_want).exclude(toppings_i_do_not_want)

这将导致我想要的结果,但是如果我仅对两个浇头感兴趣,但必须将〜100,000个其他浇头传递到排除过滤器中,则这样的查询的性能将遭受极大的损害。

有更好的方法吗?

1 个答案:

答案 0 :(得分:0)

我们可以计算pepperonisausage的浇头数量,并将其与相关浇头的总数进行比较(如果两者匹配,且数量大于0) ,那么我们可以退回这样的比萨饼:

from django.db.models import Count, Q

Pizza.objects.annotate(
    ntopping=Count('toppings')
).filter(
    ntopping__gte=1,
    ntopping=Count('toppings', filter=Q(toppings__in=[pepperoni, sausage]))
)

将完全按照您的要求做。它将返回Pizza条记录,其记录“列表[pepperoni, sausage]中存在相关的开头”。因此,对于具有pepperoni浇头,sausage浇头或两个浇头的比萨饼。