Django ALL в запросе (в отличие от предложения OR / plain IN) - PullRequest
0 голосов
/ 11 июня 2018

Скажем, у меня есть 2 модели, соединенные через многие ко многим:

class Person(models.Model):
   name = models.CharField(max_length=200, null=False, blank=False)
   sports = models.ManyToManyField('Sport')

class Sport(models.Model):
   name = models.CharField(max_length=200, null=False, blank=False)
   people = models.ManyToManyField('Person')

Я хотел бы выполнить запрос AND, чтобы отфильтровать Person по тем, кто занимается ВСЕМИ видами спорта, учитывая список спортивных идентификаторов.Так что-то вроде:

Person.objects.filter(sports__id__all=[1,2,3])

Или, иначе говоря, исключить любого, кто не занимается ВСЕМИ видами спорта.

1 Ответ

0 голосов
/ 11 июня 2018

Фильтрация (сохранение People, которые воспроизводят все данные Sport с)

Решение не тривиально.Однако если вы можете рассчитать длину списка, то это можно сделать, рассчитав число перекрытий между списком видов спорта и sports a Person воспроизведений.:

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

sports_list = [1, 2, 3]

Person.objects.filter(
    <b>sports__in=sports_list</b>
).annotate(
    <b>overlap=Count('sports')</b>
).filter(<b>overlap=len(sports_list)</b>)

Так что, если число видов спорта Person в sports_list равно количеству элементов в sports_list, то мы знаем, что человек играет все эти виды спорта.

неуникальные Sport s в sport_list

Обратите внимание, что sport_lists должен содержать уникальных Sport объектов (илиидентификаторы).Однако вы можете построить набор из sports, например:

# in case a sport can occur *multiple times in the list

from django.db.models import Count

sports<b>_set</b> = <b>set(</b>[1, 2, 3, 2, 3, 3]<b>)</b>

Person.objects.filter(
    sports__in=sports<b>_set</b>
).annotate(
    overlap=Count('sports')
).filter(overlap=len(sports_<b>_set</b>))

SQL-запрос

За шторами мы создадим запрос наподобие:

SELECT `person`.*
FROM `person`
INNER JOIN `person_sport` ON `person`.`id` = `person_sport`.`person_id`
WHERE `person_sport`.`sport_id` IN (1, 2, 3)
GROUP BY `person`.`id`
HAVING COUNT(`person_sport`.`sport_id`) = 3

Исключая (сохраняя People, которые не воспроизводят все учитывая Sport с)

Возможно, проблема связана с исключить тех лиц: лиц, которые занимаются всеми указанными видами спорта.Мы можем сделать это также, но тогда может возникнуть проблема: люди, которые вообще не занимаются спортом, также будут исключены, так как первый .filter(..) удалит этих людей.Однако мы можем немного изменить код, чтобы они также были включены:

# opposite problem: excluding those people

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

sports<b>_set</b> = <b>set(</b>[1, 2, 3, 2, 3, 3]<b>)</b>

Person.objects.filter(
    <b>Q(</b>sports__in=sports_set<b>) | Q(sports__isnull=True)</b>
).annotate(
    overlap=Count('sports')
)<b>.exclude</b>(overlap=len(sports__set))
...