Цепочка множественного фильтра () в Django, это ошибка? - PullRequest
74 голосов
/ 17 ноября 2011

Я всегда предполагал, что объединение нескольких вызовов filter () в Django всегда было таким же, как сбор их за один вызов.

# Equivalent
Model.objects.filter(foo=1).filter(bar=2)
Model.objects.filter(foo=1,bar=2)

но я столкнулся со сложным набором запросов в моем коде, где это не так

class Inventory(models.Model):
    book = models.ForeignKey(Book)

class Profile(models.Model):
    user = models.OneToOneField(auth.models.User)
    vacation = models.BooleanField()
    country = models.CharField(max_length=30)

# Not Equivalent!
Book.objects.filter(inventory__user__profile__vacation=False).filter(inventory__user__profile__country='BR')
Book.objects.filter(inventory__user__profile__vacation=False, inventory__user__profile__country='BR')

Сгенерированный SQL

SELECT "library_book"."id", "library_book"."asin", "library_book"."added", "library_book"."updated" FROM "library_book" INNER JOIN "library_inventory" ON ("library_book"."id" = "library_inventory"."book_id") INNER JOIN "auth_user" ON ("library_inventory"."user_id" = "auth_user"."id") INNER JOIN "library_profile" ON ("auth_user"."id" = "library_profile"."user_id") INNER JOIN "library_inventory" T5 ON ("library_book"."id" = T5."book_id") INNER JOIN "auth_user" T6 ON (T5."user_id" = T6."id") INNER JOIN "library_profile" T7 ON (T6."id" = T7."user_id") WHERE ("library_profile"."vacation" = False  AND T7."country" = BR )
SELECT "library_book"."id", "library_book"."asin", "library_book"."added", "library_book"."updated" FROM "library_book" INNER JOIN "library_inventory" ON ("library_book"."id" = "library_inventory"."book_id") INNER JOIN "auth_user" ON ("library_inventory"."user_id" = "auth_user"."id") INNER JOIN "library_profile" ON ("auth_user"."id" = "library_profile"."user_id") WHERE ("library_profile"."vacation" = False  AND "library_profile"."country" = BR )

Первый набор запросов со связанными вызовами filter() соединяет модель Inventory дважды, эффективно создавая ИЛИ между двумя условиями, тогда как второй набор запросов И объединяет два условия вместе. Я ожидал, что первый запрос будет также И два условия. Это ожидаемое поведение или это ошибка в Django?

Ответ на связанный вопрос Есть ли недостаток в использовании ".filter (). Filter (). Filter () ..." в Django? указывает на то, что два набора запросов должны быть эквивалент.

Ответы [ 4 ]

83 голосов
/ 17 ноября 2011

Насколько я понимаю, они немного отличаются по своему замыслу (и я, безусловно, открыт для исправления): filter(A, B) сначала отфильтрует в соответствии с A, а затем отфильтрует в соответствии с B, в то время как filter(A).filter(B) вернет строку которая соответствует A 'и' потенциально другой строке, которая соответствует B.

Посмотрите на пример здесь:

https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships

в частности:

Все в одном вызове filter () применяется одновременно для фильтрации элементов, соответствующих всем этим требованиям. Последовательные вызовы filter () дополнительно ограничивают набор объектов

...

В этом втором примере (filter (A) .filter (B)) первый фильтр ограничил набор запросов (A). Второй фильтр ограничил набор блогов тем, которые также являются (B). Записи, выбранные вторым фильтром, могут совпадать или не совпадать с записями в первом фильтре.

48 голосов
/ 31 января 2015

Эти два стиля фильтрации в большинстве случаев эквивалентны, но когда запросы к объектам основаны на ForeignKey или ManyToManyField, они немного отличаются.

Примеры из документации .

модель
Blog to Entry является отношением один-ко-многим.

from django.db import models

class Blog(models.Model):
    ...

class Entry(models.Model):
    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    pub_date = models.DateField()
    ...

объектов
Предполагая, что естьнекоторые объекты блогов и записей здесь.
enter image description here

запросов

Blog.objects.filter(entry__headline_contains='Lennon', 
    entry__pub_date__year=2008)
Blog.objects.filter(entry__headline_contains='Lennon').filter(
    entry__pub_date__year=2008)  

Для 1-го запроса (один фильтр один) он соответствует только blog1.

Для второго запроса (один из связанных фильтров) он отфильтровывает blog1 и blog2.
Первый фильтр ограничивает набор запросов blog1, blog2 и blog5;второй фильтр ограничивает набор блогов далее blog1 и blog2.

И вы должны понимать, что

Мы фильтруем элементы блога с каждым оператором фильтра, а не с элементами Entry.

Так что это не одно и то же, потому что блог и запись - это многозначные отношения.

Ссылка: https://docs.djangoproject.com/en/1.8/topics/db/queries/#spanning-multi-valued-relationships
Если что-то не так, исправьте меня.

Редактировать:Изменен v1.6 на v1.8, так как ссылки 1.6 больше не доступны.

4 голосов
/ 14 июня 2012

Как видно из сгенерированных операторов SQL, разница не в «ИЛИ», как могут подозревать некоторые. Так ГДЕ и СОЕДИНЯЕТСЯ.

Пример 1 (та же самая объединенная таблица):

(пример из https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships)

Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

Это даст вам все блоги, которые имеют одну запись с обоими (entry_ headline _contains = 'Lennon') И (entry__pub_date__year = 2008), чего вы и ожидаете из этого запроса. Результат: Книга с {entry.headline: 'Жизнь Леннона', entry.pub_date: '2008'}

Пример 2 (прикован)

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

Это будет охватывать все результаты из примера 1, но будет генерировать немного больший результат. Потому что сначала он фильтрует все блоги с (entry_ headline _contains = 'Lennon'), а затем с фильтрами результатов (entry__pub_date__year = 2008).

Разница в том, что он также даст вам такие результаты, как: Книга с {entry.headline: ' Lennon ', entry.pub_date: 2000}, {entry.headline: 'Bill', entry.pub_date: 2008 }

В вашем случае

Я думаю, вам нужен именно этот:

Book.objects.filter(inventory__user__profile__vacation=False, inventory__user__profile__country='BR')

А если вы хотите использовать ИЛИ, пожалуйста, прочитайте: https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects

0 голосов
/ 27 апреля 2019

Иногда вы не хотите объединять несколько фильтров, например:

def your_dynamic_query_generator(self, event: Event):
    qs \
    .filter(shiftregistrations__event=event) \
    .filter(shiftregistrations__shifts=False)

И следующий код на самом деле не вернет правильную вещь.

def your_dynamic_query_generator(self, event: Event):
    return Q(shiftregistrations__event=event) & Q(shiftregistrations__shifts=False)

Что вы можетесделать сейчас - использовать фильтр подсчета аннотаций.

В этом случае мы подсчитываем все смены, которые относятся к определенному событию.

qs: EventQuerySet = qs.annotate(
    num_shifts=Count('shiftregistrations__shifts', filter=Q(shiftregistrations__event=event))
)

После этого вы можете фильтровать по аннотации.

def your_dynamic_query_generator(self):
    return Q(num_shifts=0)

Это решение также дешевле для больших наборов запросов.

Надеюсь, это поможет.

...