Django фильтрует отношение m2m по списку входных данных (которые все должны совпадать)
Давайте возьмем несколько моделей Store
и Book
в качестве примеров:
class Book(Model):
title = CharField(...)
...
class Store(Model):
books = ManyToManyField('Book', blank=True, related_name='stores')
....
Я получаю список названий книг и должен вернуть магазины, связанные с этими книгами.
Мне нужна опция как для запроса AND
, так и для запроса OR
.
Запрос OR
довольно прост; нам нужно, чтобы хранилище совпадало только один раз:
Store.objects.filter(book__title__in=book_titles)
Однако запрос AND
кажется сложным. Возможно, я просто слишком углублен, чтобы заметить, но до сих пор я справлялся только с помощью цепочки запросов, что не очень хорошо, по крайней мере, с точки зрения производительности.
from django.db.models import Q
filtering = Q()
for book_title in book_title_list:
filtering &= Q(id__in=Book.objects.get(title=book_title).stores)
Store.objects.filter(filtering)
Это фактически создает OUTER JOIN
и SELECT
в предложении WHERE
для каждого названия книги, что при значении 2 или 3 не так уж много, но определенно нежелательно, если пользовательский ввод не ограничен.
Без явного зацикливания и добавления Q
объектов, подобных этому, мне еще предстоит получить запрос, который действительно работает.
Чаще всего запрос либо оценивает только одну строку отношения m2m, либо ведет себя аналогично запросу OR
. Напомним, что для выполнения запроса AND
необходимо, чтобы все возвращенные хранилища были связаны с всеми книгами, названия которых были указаны.
Я пробовал вмешиваться в классы и методы Subquery
, CASE
, WHERE
и annotate
, предоставляемые Django, но я ни в коем случае не эксперт и столкнулся только с неудачей.
В какой-то момент я подумал, что использование annotate
с увеличивающимся значением для каждого совпадения (учитывая, что длина списка известна) могло бы сработать, но мне еще предстоит найти какой-то способ заставить это работать.
Для дополнительной сложности я также не могу выполнить цикл или получить доступ к любому из наборов запросов, поскольку количество элементов неизвестно, но определенно составляет более 100 тысяч.
Вы можете проверить, совпадают ли все book
с:
from django.db.models import Count
book_titles_set = set(book_titles)
Store.objects.filter(book__title__in=book_titles_set).annotate(
nbook=Count('book')
).filter(nbook=len(book_titles_set))