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

Posted

技术标签:

【中文标题】根据特定的多对多关系过滤 Django 查询集【英文标题】:Filter a Django queryset based on specific manytomany relationships 【发布时间】:2020-01-24 21:25:51 【问题描述】:

我有类似于 Django 文档中的披萨示例的模型:

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】:

我们可以统计pepperonisausage的toppings个数,并与相关toppings的总数比较,如果两者匹配,并且数量大于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]))
)

将完全按照您的意愿行事。它将返回“列表[pepperoni, sausage] 中存在相关的顶部”的Pizza 记录。因此,对于具有pepperoni 浇头、sausage 浇头或两种浇头的比萨饼。

【讨论】:

我只想要有意大利辣香肠馅料、香肠馅料或意大利辣香肠和香肠馅料的比萨饼。不是有意大利辣香肠、香肠和橄榄的比萨饼。我将编辑问题以澄清这一点。 @snchrk: 这正是这个查询集将检索的内容,因为我们计算了 total 的浇头数量,并说它应该与当前的浇头数量相同只有意大利辣香肠和香肠。 我明白了。我误解了过滤器 Q 查询在做什么。这确实有效。谢谢。您的回答结尾(“或这些浇头和一些额外的浇头”)向我暗示这将与我的(已编辑)问题中的 p5 匹配,但事实并非如此。 @snchrk:谢谢。我想我开始写一个不同的答案,并在中途改变了它:)

以上是关于根据特定的多对多关系过滤 Django 查询集的主要内容,如果未能解决你的问题,请参考以下文章

如何在 django 中过滤查询集的多对多

Django如何过滤多对多字段中的对象,而不是原始查询集

如何过滤和访问 Django QuerySet 中的多对多字段?

从 Django 查询中的多对多关系中检索项目

针对特定集合的多对多关系核心数据查询

刀片模板中的多对多关系中的 Laravel 嵌套查询