summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorJacob Walls <jacobtylerwalls@gmail.com>2021-12-24 09:12:39 -0500
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-12-30 08:38:04 +0100
commit6174814dbe04fb6668aa212a6cdbca765a8b0522 (patch)
treebb2542facf82a0dc263ebcae32eb2e61cf17e00e /docs
parent1283458baae482c2bbb22c842b1db6c832c953f3 (diff)
Fixed #27936 -- Rewrote spanning multi-valued relationships docs.
Diffstat (limited to 'docs')
-rw-r--r--docs/topics/db/queries.txt100
1 files changed, 61 insertions, 39 deletions
diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt
index d32bca9e30..23326fb778 100644
--- a/docs/topics/db/queries.txt
+++ b/docs/topics/db/queries.txt
@@ -532,55 +532,77 @@ those latter objects, you could write::
Spanning multi-valued relationships
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-When you are filtering an object based on a
-:class:`~django.db.models.ManyToManyField` or a reverse
-:class:`~django.db.models.ForeignKey`, there are two different sorts of filter
-you may be interested in. Consider the ``Blog``/``Entry`` relationship
-(``Blog`` to ``Entry`` is a one-to-many relation). We might be interested in
-finding blogs that have an entry which has both *"Lennon"* in the headline and
-was published in 2008. Or we might want to find blogs that have an entry with
-*"Lennon"* in the headline as well as an entry that was published
-in 2008. Since there are multiple entries associated with a single ``Blog``,
-both of these queries are possible and make sense in some situations.
+When spanning a :class:`~django.db.models.ManyToManyField` or a reverse
+:class:`~django.db.models.ForeignKey` (such as from ``Blog`` to ``Entry``),
+filtering on multiple attributes raises the question of whether to require each
+attribute to coincide in the same related object. We might seek blogs that have
+an entry from 2008 with *“Lennon”* in its headline, or we might seek blogs that
+merely have any entry from 2008 as well as some newer or older entry with
+*“Lennon”* in its headline.
-The same type of situation arises with a
-:class:`~django.db.models.ManyToManyField`. For example, if an ``Entry`` has a
-:class:`~django.db.models.ManyToManyField` called ``tags``, we might want to
-find entries linked to tags called *"music"* and *"bands"* or we might want an
-entry that contains a tag with a name of *"music"* and a status of *"public"*.
-
-To handle both of these situations, Django has a consistent way of processing
-:meth:`~django.db.models.query.QuerySet.filter` calls. Everything inside a
-single :meth:`~django.db.models.query.QuerySet.filter` call is applied
-simultaneously to filter out items matching all those requirements. Successive
-:meth:`~django.db.models.query.QuerySet.filter` calls further restrict the set
-of objects, but for multi-valued relations, they apply to any object linked to
-the primary model, not necessarily those objects that were selected by an
-earlier :meth:`~django.db.models.query.QuerySet.filter` call.
-
-That may sound a bit confusing, so hopefully an example will clarify. To
-select all blogs that contain entries with both *"Lennon"* in the headline
-and that were published in 2008 (the same entry satisfying both conditions),
-we would write::
+To select all blogs containing at least one entry from 2008 having *"Lennon"*
+in its headline (the same entry satisfying both conditions), we would write::
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
-To select all blogs that contain an entry with *"Lennon"* in the headline
-**as well as** an entry that was published in 2008, we would write::
+Otherwise, to perform a more permissive query selecting any blogs with merely
+*some* entry with *"Lennon"* in its headline and *some* entry from 2008, we
+would write::
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
-Suppose there is only one blog that had both entries containing *"Lennon"* and
+Suppose there is only one blog that has both entries containing *"Lennon"* and
entries from 2008, but that none of the entries from 2008 contained *"Lennon"*.
The first query would not return any blogs, but the second query would return
-that one blog.
+that one blog. (This is because the entries selected by the second filter may
+or may not be the same as the entries in the first filter. We are filtering the
+``Blog`` items with each filter statement, not the ``Entry`` items.) In short,
+if each condition needs to match the same related object, then each should be
+contained in a single :meth:`~django.db.models.query.QuerySet.filter` call.
+
+.. note::
+
+ As the second (more permissive) query chains multiple filters, it performs
+ multiple joins to the primary model, potentially yielding duplicates.
-In the second example, the first filter restricts the queryset to all those
-blogs linked to entries with *"Lennon"* in the headline. The second filter
-restricts the set of blogs *further* to those that are also linked to entries
-that were published in 2008. The entries selected by the second filter may or
-may not be the same as the entries in the first filter. We are filtering the
-``Blog`` items with each filter statement, not the ``Entry`` items.
+ >>> from datetime import date
+ >>> beatles = Blog.objects.create(name='Beatles Blog')
+ >>> pop = Blog.objects.create(name='Pop Music Blog')
+ >>> Entry.objects.create(
+ ... blog=beatles,
+ ... headline='New Lennon Biography',
+ ... pub_date=date(2008, 6, 1),
+ ... )
+ <Entry: New Lennon Biography>
+ >>> Entry.objects.create(
+ ... blog=beatles,
+ ... headline='New Lennon Biography in Paperback',
+ ... pub_date=date(2009, 6, 1),
+ ... )
+ <Entry: New Lennon Biography in Paperback>
+ >>> Entry.objects.create(
+ ... blog=pop,
+ ... headline='Best Albums of 2008',
+ ... pub_date=date(2008, 12, 15),
+ ... )
+ <Entry: Best Albums of 2008>
+ >>> Entry.objects.create(
+ ... blog=pop,
+ ... headline='Lennon Would Have Loved Hip Hop',
+ ... pub_date=date(2020, 4, 1),
+ ... )
+ <Entry: Lennon Would Have Loved Hip Hop>
+ >>> Blog.objects.filter(
+ ... entry__headline__contains='Lennon',
+ ... entry__pub_date__year=2008,
+ ... )
+ <QuerySet [<Blog: Beatles Blog>]>
+ >>> Blog.objects.filter(
+ ... entry__headline__contains='Lennon',
+ ... ).filter(
+ ... entry__pub_date__year=2008,
+ ... )
+ <QuerySet [<Blog: Beatles Blog>, <Blog: Beatles Blog>, <Blog: Pop Music Blog]>
.. note::