разница между фильтром с несколькими аргументами и цепным фильтром в django

86

В чем разница между фильтром с несколькими аргументами и цепным фильтром в django?

тестмобиль
источник
Может ли фильтр иметь несколько аргументов? stackoverflow.com/questions/420703/…
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

Ответы:

60

Как вы можете видеть в сгенерированных операторах SQL, разница не в «ИЛИ», как некоторые могут подумать. Так размещаются 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), чего и следовало ожидать от этого запроса.

Результат:

Blog with {entry.headline: 'Life of Lennon', 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**}

Когда был оценен первый фильтр, книга включается из-за первой записи (даже если в ней есть другие записи, которые не совпадают). При оценке второго фильтра книга включается из-за второй записи.

Одна таблица: Но если запрос не включает объединенные таблицы, как в примере от Yuji и DTing. Результат такой же.

Джонни Цанг
источник
21
Я предполагаю, что сегодня утром я просто тупой, но это предложение меня сбивает с толку: «Поскольку оно сначала фильтрует все блоги, (entry__headline__contains='Lennon')а затем фильтры результатов (entry__pub_date__year=2008)» «Если« то из результата »верно, то почему оно будет включать что-то с entry.headline == 'Bill'.. .не удалось entry__headline__contains='Lennon'отфильтровать Billэкземпляр?
Dustin Wyatt
7
Я тоже в замешательстве. Кажется, что этот ответ неверен, но за него 37 голосов ...
Personman
1
Этот ответ вводит в заблуждение и сбивает с толку, обратите внимание, что приведенное выше верно только при фильтрации с использованием отношений M2M, как указано в ответе Yuji. Ключевым моментом является пример фильтрации элементов блога с помощью каждого оператора фильтра, а не элементов входа.
диктор
1
Потому что в одном блоге может быть несколько записей. Язык правильный. Эта концепция может сбить с толку, если вы не будете помнить все движущиеся части.
DylanYoung
@DustinWyatt У меня тоже были те же вопросы, что и у вас, но я наконец понял их! См. Пример «Сотрудник и иждивенец», написанный Гриешем Чауханом ниже на этой странице, и вы тоже его получите.
theQuestionMan
33

Случай, в котором результаты «запрос-фильтр с несколькими аргументами» отличается от «запрос-фильтр-цепочки», следующий:

Выбор ссылочных объектов на основе ссылок на объекты и отношения осуществляется по принципу «один ко многим» (или «многие ко многим»).

Несколько фильтров:

    Referenced.filter(referencing1_a=x, referencing1_b=y)
    #  same referencing model   ^^                ^^

Связанные фильтры:

    Referenced.filter(referencing1_a=x).filter(referencing1_b=y)

Оба запроса могут выдавать разные результаты:
если более одной строки в referencing-model Referencing1могут ссылаться на одну и ту же строку в referenced-model Referenced. Это может быть в случае Referenced: Referencing1иметь или 1: N (один ко многим) или N: M (многие ко многим) отношение корабль.

Пример:

Считайте, что в моем приложении my_companyесть две модели Employeeи Dependent. У сотрудника my_companyможет быть больше, чем иждивенцев (другими словами, иждивенец может быть сыном / дочерью одного сотрудника, в то время как у сотрудника может быть более одного сына / дочери).
Эхх, при условии, что как муж и жена, оба не могут работать в a my_company. Я взял пример 1: м

Итак, Employeeэто ссылочная модель, на которую может ссылаться больше, чем Dependentссылочная модель. Теперь рассмотрим состояние отношения следующим образом:

Employee:        Dependent:
+------+        +------+--------+-------------+--------------+
| name |        | name | E-name | school_mark | college_mark |
+------+        +------+--------+-------------+--------------+
| A    |        | a1   |   A    |          79 |           81 |
| B    |        | b1   |   B    |          80 |           60 |
+------+        | b2   |   B    |          68 |           86 |
                +------+--------+-------------+--------------+  

Зависимый a1относится к сотруднику A, а зависимый - b1, b2к сотруднику B.

Теперь мой запрос:

Найти всех сотрудников, у которых сын / дочь имеют отличительные знаки (скажем,> = 75%) как в колледже, так и в школе?

>>> Employee.objects.filter(dependent__school_mark__gte=75,
...                         dependent__college_mark__gte=75)

[<Employee: A>]

Результат «А» зависит от «А1» имеет отличительные знаки как в колледже, так и в школе зависит от сотрудника «А». Примечание «B» не выбрано, потому что ни один из детей «B» не имеет отличительных оценок как в колледже, так и в школе. Реляционная алгебра:

Сотрудник (school_mark> = 75 И college_mark> = 75) В зависимости

Во-вторых, мне нужен запрос:

Найти всех сотрудников, некоторые из иждивенцев которых имеют отличия в колледже и школе?

>>> Employee.objects.filter(
...             dependent__school_mark__gte=75
...                ).filter(
...             dependent__college_mark__gte=75)

[<Employee: A>, <Employee: B>]

На этот раз «B» также выбрана, потому что у «B» двое детей (более одного!), Один имеет отличительный знак в школе «b1», а другой имеет отличительный знак в колледже «b2».
Порядок фильтрации не имеет значения, мы также можем написать запрос выше как:

>>> Employee.objects.filter(
...             dependent__college_mark__gte=75
...                ).filter(
...             dependent__school_mark__gte=75)

[<Employee: A>, <Employee: B>]

результат такой же! Реляционная алгебра может быть:

(Сотрудник (school_mark> = 75) В зависимости) (college_mark> = 75) В зависимости

Обратите внимание на следующее:

dq1 = Dependent.objects.filter(college_mark__gte=75, school_mark__gte=75)
dq2 = Dependent.objects.filter(college_mark__gte=75).filter(school_mark__gte=75)

Выводит тот же результат: [<Dependent: a1>]

Я проверяю целевой SQL-запрос, созданный Django, используя print qd1.queryиprint qd2.query оба они одинаковы (Django 1.6).

Но семантически оба для меня разные . первый выглядит как простой раздел σ [school_mark> = 75 AND college_mark> = 75] (Dependent), а второй - как медленный вложенный запрос: σ [school_mark> = 75][College_mark> = 75] (Dependent)).

Если нужен код @codepad

Кстати, это приведено в документации @ Spanning многозначных отношений. Я только что добавил пример, думаю, это будет полезно для кого-то нового.

Гриджеш Чаухан
источник
4
Спасибо за это полезное объяснение, оно лучше, чем в документации, которое совсем не понятно.
wim
1
Последняя отметка о прямой фильтрации иждивенцев очень полезна. Это показывает, что изменение результатов окончательно происходит только тогда, когда вы проходите через отношения «многие ко многим». Если вы запрашиваете таблицу напрямую, объединение фильтров в цепочку похоже на двойное объединение.
Крис
20

В большинстве случаев для запроса существует только один возможный набор результатов.

Использование цепочки фильтров возникает, когда вы имеете дело с m2m:

Учти это:

# will return all Model with m2m field 1
Model.objects.filter(m2m_field=1) 

# will return Model with both 1 AND 2    
Model.objects.filter(m2m_field=1).filter(m2m_field=2) 

# this will NOT work
Model.objects.filter(Q(m2m_field=1) & Q(m2m_field=2))

Другие примеры приветствуются.

Юдзи Томита Томита
источник
4
Другой пример: это не ограничивается m2m, это также может происходить с «один ко многим» - с обратным поиском, например, с использованием related_name в ForeignKey
wim
Спасибо за ваше объяснение! До этого я думал, что последний и второй примеры равны, поэтому последний пример у меня не работал (неверные результаты запроса), и я потратил много времени на поиски. 2-й пример очень помог мне. Также, как сказал Вим, это можно использовать с обратными отношениями «один ко многим», как в моем случае.
zen11625
12

Разница в производительности огромна. Попробуйте и убедитесь.

Model.objects.filter(condition_a).filter(condition_b).filter(condition_c)

на удивление медленнее по сравнению с

Model.objects.filter(condition_a, condition_b, condition_c)

Как упоминалось в Эффективном Django ORM ,

  • QuerySets поддерживает состояние в памяти
  • Цепочка триггеров клонирования, дублирование этого состояния
  • К сожалению, QuerySets поддерживают много состояний
  • Если возможно, не связывайте более одного фильтра
деготь
источник
8

Вы можете использовать модуль подключения, чтобы просмотреть необработанные запросы sql для сравнения. Как объяснил Юджи, по большей части они эквивалентны, как показано здесь:

>>> from django.db import connection
>>> samples1 = Unit.objects.filter(color="orange", volume=None)
>>> samples2 = Unit.objects.filter(color="orange").filter(volume=None)
>>> list(samples1)
[]
>>> list(samples2)
[]
>>> for q in connection.queries:
...     print q['sql']
... 
SELECT `samples_unit`.`id`, `samples_unit`.`color`, `samples_unit`.`volume` FROM `samples_unit` WHERE (`samples_unit`.`color` = orange  AND `samples_unit`.`volume` IS NULL)
SELECT `samples_unit`.`id`, `samples_unit`.`color`, `samples_unit`.`volume` FROM `samples_unit` WHERE (`samples_unit`.`color` = orange  AND `samples_unit`.`volume` IS NULL)
>>> 
dting
источник
3

Этот ответ основан на Django 3.1.

Окружающая обстановка

Модели

class Blog(models.Model):
    blog_id = models.CharField()

class Post(models.Model):
    blog_id  = models.ForeignKeyField(Blog)
    title    = models.CharField()
    pub_year = models.CharField() # Don't use CharField for date in production =]

Таблицы базы данных

введите описание изображения здесь

Фильтры вызова

Blog.objects.filter(post__title="Title A", post__pub_year="2020")
# Result: <QuerySet [<Blog: 1>]>

Blog.objects.filter(post__title="Title A").filter(post_pub_date="2020)
# Result: <QuerySet [<Blog: 1>, [<Blog: 2>]>

Объяснение

Прежде чем я начну что-либо дальше, я должен заметить, что этот ответ основан на ситуации, когда для фильтрации объектов используется «ManyToManyField» или обратный «ForeignKey».

Если вы используете одну и ту же таблицу или «OneToOneField» для фильтрации объектов, то не будет никакой разницы между использованием «Фильтра по нескольким аргументам» или «Цепочки фильтров». Оба они будут работать как фильтр условия «И».

Самый простой способ понять, как использовать «Фильтр с несколькими аргументами» и «Цепочка фильтров», - это запомнить в фильтре «ManyToManyField» или обратном «ForeignKey», что «Фильтр с несколькими аргументами» является условием «И» и «Фильтр» -chain »является условием« ИЛИ ».

Причина, по которой «фильтр с несколькими аргументами» и «цепочка фильтров» настолько различаются, заключается в том, что они извлекают результат из разных таблиц соединения и используют разные условия в операторе запроса.

«Фильтр по нескольким аргументам» используйте «Post». «Public_Year» = '2020' для определения публичного года.

SELECT *
FROM "Book" 
INNER JOIN ("Post" ON "Book"."id" = "Post"."book_id")
WHERE "Post"."Title" = 'Title A'
AND "Post"."Public_Year" = '2020'

В запросе к базе данных «цепочка фильтров» используется «T1». «Public_Year» = '2020' для определения публичного года.

SELECT *
FROM "Book" 
INNER JOIN "Post" ON ("Book"."id" = "Post"."book_id")
INNER JOIN "Post" T1 ON ("Book"."id" = "T1"."book_id")
WHERE "Post"."Title" = 'Title A'  
AND "T1"."Public_Year" = '2020'

Но почему разные условия влияют на результат?

Я считаю, что большинство из нас, кто заходит на эту страницу, включая меня =], придерживается того же предположения при использовании «Фильтра по нескольким аргументам» и «Цепочки фильтров».

Мы считаем, что результат должен быть получен из таблицы, подобной следующей, которая подходит для «Фильтра множественных аргументов». Так что, если вы используете «Фильтр множественных аргументов», вы получите результат, как и ожидали.

введите описание изображения здесь

Но при работе с «цепочкой фильтров» Django создает другой оператор запроса, который изменяет приведенную выше таблицу на следующую. Кроме того, «Public Year» идентифицируется в разделе «T1» вместо раздела «Post» из-за изменения инструкции запроса.

введите описание изображения здесь

Но откуда взялась эта странная диаграмма таблицы соединений «цепочка фильтров»?

Я не специалист по базам данных. Приведенное ниже объяснение - это то, что я понял до сих пор после того, как создал ту же структуру базы данных и провел тест с тем же оператором запроса.

На следующей диаграмме показано, откуда взялась эта странная диаграмма таблицы соединений «цепочка фильтров».

введите описание изображения здесь

введите описание изображения здесь

База данных сначала создаст объединенную таблицу, сопоставляя строки таблиц «Блог» и «Публикация» одну за другой.

После этого база данных снова выполняет тот же процесс сопоставления, но с использованием таблицы результатов шага 1 для сопоставления с таблицей «T1», которая является той же таблицей «Post».

Вот откуда взялась эта странная диаграмма таблицы соединений «цепочка фильтров».

Заключение

Таким образом, две вещи делают «фильтр с несколькими аргументами» и «цепочку фильтров» разными.

  1. Django создает разные операторы запроса для «Фильтр по нескольким аргументам» и «Цепочка фильтров», благодаря которым результаты «Фильтр по нескольким аргументам» и «Цепочка фильтров» поступают из разных таблиц.
  2. Оператор запроса «Цепочка фильтров» идентифицирует условие из другого места, чем «Фильтр по нескольким аргументам».

Грязный способ запомнить, как его использовать, - это «Фильтр с несколькими аргументами» - это условие «И», а «Цепочка фильтров» - это условие «ИЛИ» в фильтре «ManyToManyField» или обратном «ForeignKey».

LearnerAndLearn
источник
2

Если вы попадаете на эту страницу в поисках того, как динамически создать набор запросов django с несколькими фильтрами цепочки, но вам нужно, чтобы фильтры были ANDтипа, а не OR, рассмотрите возможность использования Q-объектов .

Пример:

# First filter by type.
filters = None
if param in CARS:
  objects = app.models.Car.objects
  filters = Q(tire=param)
elif param in PLANES:
  objects = app.models.Plane.objects
  filters = Q(wing=param)

# Now filter by location.
if location == 'France':
  filters = filters & Q(quay=location)
elif location == 'England':
  filters = filters & Q(harbor=location)

# Finally, generate the actual queryset
queryset = objects.filter(filters)
Мэтт
источник
В случае, если if или elif не переданы, переменная filter будет None, а затем вы получите TypeError: неподдерживаемые типы операндов для &: 'NoneType' и 'Q'. Я инициировал фильтры с
filter
-4

Есть разница, когда у вас есть запрос к связанному объекту, например

class Book(models.Model):
    author = models.ForeignKey(Author)
    name = models.ForeignKey(Region)

class Author(models.Model):
    name = models.ForeignKey(Region)

запрос

Author.objects.filter(book_name='name1',book_name='name2')

возвращает пустой набор

и запросить

Author.objects.filter(book_name='name1').filter(book_name='name2')

возвращает авторов, у которых есть книги с именами "имя1" и "имя2"

подробнее см. https://docs.djangoproject.com/en/dev/topics/db/queries/#s-spanning-multi-valued-relationships

Куница
источник
5
Author.objects.filter(book_name='name1',book_name='name2')даже не валидный питон, это было быSyntaxError: keyword argument repeated
wim
1
Где точно определено имя_книги? Вы имеете в виду book_set__name?
DylanYoung