Подтвердить что ты не робот

Как найти пересечение двух наборов запросов Django?

У меня есть модель 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 для других моделей.)

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

4b9b3361

Ответ 1

Рефакторинг

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

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

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

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

Ответ 3

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

intersection = queryset1 & queryset2

Чтобы сделать объединение, просто замените & на |

Ответ 4

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

Ответ 5

В соответствии с Django 1.11 теперь доступна функция intersection()

>>> qs1.intersection(qs2, qs3)

Ответ 6

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

intersection = set(queryset1) & set(queryset2)

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

Ответ 7

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

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

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

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

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).

Ответ 8

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

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)

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