Отфильтруйте набор запросов Django на основе определенных многочисленных отношений - PullRequest
1 голос
/ 24 сентября 2019

У меня есть модели, похожие на пример пиццы в документации 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 Ответ

0 голосов
/ 24 сентября 2019

Мы можем подсчитать количество начинки, равное pepperoni или sausage, и сравнить это с общим количеством связанных начинок, если два совпадения и число больше 0, то мы можем вернуть такоепицца:

from django.db.models import <b>Count, Q</b>

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

точно сделает то, что вы хотите.Он вернет Pizza записей, для которых "существует связанный топпинг, который есть в списке [pepperoni, sausage]"Так что для пиццы, у которой есть pepperoni начинка, sausage посыпка или обе начинки.

...