Решение с объектами Q
не будет работать.
Некоторые исходные данные:
>>> from subjects.models import Subject, Book
>>> s1 = Subject.objects.create(name='subject_1', description='description 1')
>>> s2 = Subject.objects.create(name='subject_2', description='description 2')
>>> s3 = Subject.objects.create(name='subject_3', description='description 3')
>>> s4 = Subject.objects.create(name='subject_4', description='description 4')
>>> b1 = Book.objects.create(title='one_subject_book', subtitle='one subject')
>>> b1.subjects = [s1]
>>> b2 = Book.objects.create(title='three_subject_book', subtitle='three subjects')
>>> b2.subjects = [s2,s3,s4]
>>> b3 = Book.objects.create(title='four_subject_book', subtitle='four subjects')
>>> b3.subjects = [s1,s2,s3,s4]
Давайте сначала проверить наивный подход с subjects__in=[s1,s2,s3]
. Мы ищем только одну книгу, потому что только b3
содержит все предметы.
>>> Book.objects.filter(subjects__in=[s1,s2,s3])
DEBUG (0.001)
SELECT `subjects_book`.`id`, `subjects_book`.`title`, `subjects_book`.`subtitle`
FROM `subjects_book`
INNER JOIN `subjects_book_subjects` ON (`subjects_book`.`id` = `subjects_book_subjects`.`book_id`)
WHERE `subjects_book_subjects`.`subject_id` IN (1, 2, 3)
LIMIT 21; args=(1, 2, 3)
[<Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>]
Мы просто ищем books
, содержащие s1
ИЛИ s2
ИЛИ s3
. Вот почему мы получаем такой результат. У нас есть повторяющиеся результаты, потому что мы не использовали .distinct()
Теперь попробуй с объектами Q
.
Сразу хочу отметить, что:
Book.objects.filter(Q(subjects=s1)&Q(subjects=s2)&Q(subjects=s3))
так же, как:
Book.objects.filter(reduce(lambda q1,q2: q1&q2, [Q(subjects=s) for s in [s1,s2,s3]]))
Просто более динамичной версии, где и можно легко изменить только список с subjects
.
>>> Book.objects.filter(reduce(lambda q1,q2: q1&q2, [Q(subjects=s) for s in [s1,s2,s3]]))
DEBUG (0.260)
SELECT `subjects_book`.`id`, `subjects_book`.`title`, `subjects_book`.`subtitle`
FROM `subjects_book`
INNER JOIN `subjects_book_subjects` ON (`subjects_book`.`id` = `subjects_book_subjects`.`book_id`)
WHERE (
`subjects_book_subjects`.`subject_id` = 1
AND `subjects_book_subjects`.`subject_id` = 2
AND `subjects_book_subjects`.`subject_id` = 3
) LIMIT 21; args=(1, 2, 3)
[]
Мы получили пустой набор результатов. Это было бы нормально, если вы проверили запрос. Мы не можем иметь строку, содержащую три разных значения subject_id
одновременно. Причина, по которой мы получаем такой запрос, заключается в том, что мы применяем все фильтры Q
сразу в одном выражении .filter
. Узнайте больше об этом на странице Spanning multi-valued relationships.
Чтобы получить правильный результат от MySQL
, мы должны ПРИСОЕДИНЯЙТЕСЬ К subjects_book_subjects
таблице на каждый subject
, на который мы хотим отфильтровать. В ORM
перспективе это может быть достигнуто с помощью серии последовательных .filter
заявлений:
Book.objects.filter(subjects=s1).filter(subjects=s2).filter(subjects=s3)
В более моды образом это выглядит так:
books_queryset = reduce(lambda books,s: books.filter(subjects=s),[s1,s2,s3], Book.objects.all())
как пример:
>>> reduce(lambda books,s: books.filter(subjects=s),[s1,s2,s3], Book.objects.all())
DEBUG (0.001)
SELECT `subjects_book`.`id`, `subjects_book`.`title`, `subjects_book`.`subtitle`
FROM `subjects_book`
INNER JOIN `subjects_book_subjects` ON (`subjects_book`.`id` = `subjects_book_subjects`.`book_id`)
INNER JOIN `subjects_book_subjects` T4 ON (`subjects_book`.`id` = T4.`book_id`)
INNER JOIN `subjects_book_subjects` T6 ON (`subjects_book`.`id` = T6.`book_id`)
WHERE (
`subjects_book_subjects`.`subject_id` = 1
AND T4.`subject_id` = 2
AND T6.`subject_id` = 3
) LIMIT 21; args=(1, 2, 3)
[<Book: Book object>]
Это предположим, но работает 'Book.objects.filter (subject__in = [2, 3, 6])'? – fletom
Это показывает все книги, имеющие предметы id 2 OR книги с предметами id 3 ИЛИ книги с предметами id 6 – wrufesh
Затем что-то вроде 'Book.objects.filter (subject__id = 2) .filter (subject__id = 3) .filter (subject__id = 6) '? Вы должны добавить их в цикл for. – fletom