Как я могу найти пересечение двух наборов запросов Django? - PullRequest
24 голосов
/ 10 декабря 2010

У меня есть модель Django с двумя пользовательскими методами менеджера. Каждый возвращает различное подмножество объектов модели, основанное на различном свойстве объекта.

class FeatureManager(models.Manager):

    def without_test_cases(self):
        return self.get_query_set().annotate(num_test_cases=models.Count('testcase_set')).filter(num_test_cases=0)

    def standardised(self):
        return self.get_query_set().annotate(standardised=Count('documentation_set__standard')).filter(standardised__gt=0)

(Обе testcase_set и documentation_set относятся к ManyToManyField с на других моделях.)

Есть ли способ получить набор запросов или просто список объектов, который является пересечением наборов запросов, возвращаемых каждым методом менеджера?

Ответы [ 8 ]

50 голосов
/ 10 декабря 2010

В большинстве случаев вы можете просто написать (используя часть «Набор» QuerySet):

intersection = Model.objects.filter(...) & Model.objects.filter(...)

Это не очень хорошо задокументировано, но должно вести себя почти так же, как использование условий AND в условиях обоих запросов. Соответствующий код: https://github.com/django/django/blob/1.8c1/django/db/models/query.py#L203

22 голосов
/ 14 августа 2013

Вы можете просто сделать что-то вроде этого:

intersection = queryset1 & queryset2

Для объединения просто замените & на |

14 голосов
/ 31 мая 2017

Согласно Django 1.11, теперь доступна функция пересечение ()

>>> qs1.intersection(qs2, qs3)
4 голосов
/ 04 ноября 2012

Я считаю, что qs1.filter (pk__in = qs2) должен работать (обычно).Кажется, это работает для аналогичного случая для меня, имеет смысл, что он будет работать, и сгенерированный запрос выглядит вменяемым.(Если один из ваших наборов запросов использует values ​​(), чтобы не выбирать столбец первичного ключа или что-то странное, я могу поверить, что он сломается, хотя ...)

3 голосов
/ 10 декабря 2010

Refactor

class FeatureManager(models.Manager):

    @staticmethod
    def _test_cases_eq_0( qs ):
       return qs.annotate( num_test_cases=models.Count('testcase_set') ).filter(num_test_cases=0)

    @staticmethod
    def _standardized_gt_0( qs ):
        return qs.annotate( standardised=Count('documentation_set__standard') ).filter(standardised__gt=0)

    def without_test_cases(self):
        return self._test_cases_eq_0( self.get_query_set() )

    def standardised(self):
        return self._standardized_gt_0( self.get_query_set() )

    def intersection( self ):
        return self._test_cases_eq_0( self._standardized_gt_0( self.get_query_set() ) )
2 голосов
/ 10 декабря 2010

Если вы хотите сделать это на python, а не в базе данных:

intersection = set(queryset1) & set(queryset2)

Проблема в том, что если вы используете разные аннотации в запросах из-за добавленных аннотаций, объекты могут выглядеть по-разному ...

0 голосов
/ 10 декабря 2010

Если вы на самом деле просто используете аннотацию для фильтрации на основе того, является ли счет нулевым или нет, тогда это должно работать вместо:

class FeatureManager(models.Manager):

    def without_test_cases(self):
        return self.get_query_set().filter(testcase__pk__isnull=True)

    def standardised(self):
        return self.get_query_set().filter(documentation_set__standard__isnull=False)

Поскольку вы больше не беспокоитесь о аннотациях, эти два запроса должны пересекаться очень плавно.

0 голосов
/ 10 декабря 2010

Одним из способов может быть использование модуля наборов python и просто пересечение:

создать пару наборов запросов, которые перекрываются при id = 5:

In [42]: first = Location.objects.filter(id__lt=6)
In [43]: last = Location.objects.filter(id__gt=4)

"импорт наборов"сначала (получает предупреждение об устаревании ... ммм ... о хорошо).Теперь создайте и пересекайте их - мы получаем один элемент в наборе:

In [44]: sets.Set(first).intersection(sets.Set(last))
Out[44]: Set([<Location: Location object>])

Теперь получаем идентификатор элементов пересечения, чтобы проверить, действительно ли он равен 5:

In [48]: [s.id for s in sets.Set(first).intersection(sets.Set(last))]
Out[48]: [5]

Это, очевидно, поражаетбаза данных дважды и возвращает все элементы набора запросов. Лучше было бы объединить фильтры в ваших менеджерах, и это должно быть в состоянии сделать это за одно обращение к БД и на уровне SQL.Я не вижу метод QuerySet.and / или (QuerySet).

...