Django: сопоставление фильтров ManyToMany по всем элементам в списке
У меня есть такая модель книги:
class Book(models.Model):
authors = models.ManyToManyField(Author, ...)
...
Короче:
Я хотел бы получить книги, авторы которых строго равны заданному набору авторов. Я не уверен, что есть один запрос, который делает это, но любые предложения будут полезны.
В длинном:
Вот что я пробовал (не удалось запустить Get AttributeError)
# A sample set of authors
target_authors = set((author_1, author_2))
# To reduce the search space,
# first retrieve those books with just 2 authors.
candidate_books = Book.objects.annotate(c=Count('authors')).filter(c=len(target_authors))
final_books = QuerySet()
for author in target_authors:
temp_books = candidate_books.filter(authors__in=[author])
final_books = final_books and temp_books
... и вот что я получил:
AttributeError: 'NoneType' object has no attribute '_meta'
В общем, как я должен запрашивать модель с ограничением, что ее поле ManyToMany содержит набор заданных объектов, как в моем случае?
ps: Я нашел некоторые актуальные вопросы SO, но не смог получить четкого ответа. Любой хороший указатель будет полезен. Спасибо.
Ответы
Ответ 1
Подобно подходу @goliney, я нашел решение. Однако я думаю, что эффективность может быть улучшена.
# A sample set of authors
target_authors = set((author_1, author_2))
# To reduce the search space, first retrieve those books with just 2 authors.
candidate_books = Book.objects.annotate(c=Count('authors')).filter(c=len(target_authors))
# In each iteration, we filter out those books which don't contain one of the
# required authors - the instance on the iteration.
for author in target_authors:
candidate_books = candidate_books.filter(authors=author)
final_books = candidate_books
Ответ 2
Вы можете использовать сложные поисковые запросы с объектами Q
from django.db.models import Q
...
target_authors = set((author_1, author_2))
q = Q()
for author in target_authors:
q &= Q(authors=author)
Books.objects.annotate(c=Count('authors')).filter(c=len(target_authors)).filter(q)
Ответ 3
Я столкнулся с одной и той же проблемой и пришел к тому же выводу, что и iyysal,
пока мне не пришлось делать поиск среднего размера (с 1000 записями с 150 фильтрами мой запрос будет тайм-аут).
В моем конкретном случае поиск приведет к отсутствию записей, поскольку вероятность того, что одна запись будет выровнена со всеми фильтрами 150 150, очень редка, вы можете обойти проблемы с производительностью, проверив, что есть записи в QuerySet, прежде чем применять больше фильтры для экономии времени.
# In each iteration, we filter out those books which don't contain one of the
# required authors - the instance on the iteration.
for author in target_authors:
if candidate_books.count() > 0:
candidate_books = candidate_books.filter(authors=author)
По какой-то причине Django применяет фильтры для удаления QuerySets.
Но если оптимизация должна применяться правильно, однако, необходимо использовать подготовленный QuerySet и правильно применяемые индексы.
Ответ 4
Q() и Q() не равно .filter(). Filter(). Их необработанные SQL отличаются, когда с помощью Q с & его SQL просто добавляет условие, например, WHERE "book"."author" = "author_1" and "book"."author" = "author_2"
. он должен вернуть пустой результат.
Единственное решение - просто создать фильтр цепочки для формирования SQL с внутренним объединением в одной таблице: ... ON ("author"."id" = "author_book"."author_id") INNER JOIN "author_book" T4 ON ("author"."id" = T4."author_id") WHERE ("author_book"."author_id" = "author_1" AND T4."author_id" = "author_1")