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

Связать несколько фильтров() в Django, это ошибка?

Я всегда предполагал, что цепочка вызовов с несколькими фильтрами() в 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, создавая OR между этими двумя условиями, тогда как второй набор запросов AND объединяет два условия. Я ожидал, что первый запрос будет также и двумя условиями. Является ли это ожидаемым поведением или это ошибка в Django?

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

4b9b3361

Ответ 1

Насколько я понимаю, они немного отличаются по своей конструкции (и я, безусловно, открыт для исправления): 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() дополнительно ограничивают набор объектов

...

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

Ответ 2

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

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

модель
Блог для входа - отношение "один ко многим".

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)  

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

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

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

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

Итак, это не то же самое, потому что Blog и Entry являются многозначными отношениями.

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

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

Ответ 3

Как вы можете видеть в сгенерированных операторах SQL различие не является "OR", как могут подозревать некоторые. Именно так размещаются WHERE и JOIN.

Пример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') AND (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')

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

Ответ 4

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

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)

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

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