Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changed "Don't overuse count() or exists()" example to Python #13294

Merged
merged 1 commit into from Jan 27, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
56 changes: 24 additions & 32 deletions docs/topics/db/optimization.txt
Expand Up @@ -260,47 +260,39 @@ Don't overuse ``count()`` and ``exists()``

If you are going to need other data from the QuerySet, evaluate it immediately.

For example, assuming an Email model that has a ``body`` attribute and a
many-to-many relation to User, the following template code is optimal:

.. code-block:: html+django

{% if display_inbox %}
{% with emails=user.emails.all %}
{% if emails %}
<p>You have {{ emails|length }} email(s)</p>
{% for email in emails %}
<p>{{ email.body }}</p>
{% endfor %}
{% else %}
<p>No messages today.</p>
{% endif %}
{% endwith %}
{% endif %}

For example, assuming an Email model that has a ``subject`` attribute and a
many-to-many relation to User, the following code is optimal::

if display_emails:
emails = user.emails.all()
if emails:
print('You have', len(emails), 'emails:')
for email in emails:
print(email.subject)
else:
print('You do not have any emails.')

It is optimal because:

#. Since QuerySets are lazy, this does no database queries if 'display_inbox'
is False.
#. Since QuerySets are lazy, this does no database queries if
``display_emails`` is ``False``.

#. Use of :ttag:`with` means that we store ``user.emails.all`` in a variable
for later use, allowing its cache to be re-used.
#. Storing ``user.emails.all()`` in the ``emails`` variable allows its result
cache to be re-used.

#. The line ``{% if emails %}`` causes ``QuerySet.__bool__()`` to be called,
which causes the ``user.emails.all()`` query to be run on the database, and
at the least the first line to be turned into an ORM object. If there aren't
any results, it will return False, otherwise True.
#. The line ``if emails`` causes ``QuerySet.__bool__()`` to be called, which
causes the ``user.emails.all()`` query to be run on the database. If there
aren't any results, it will return ``False``, otherwise ``True``.

#. The use of ``{{ emails|length }}`` calls ``QuerySet.__len__()``, filling
out the rest of the cache without doing another query.
#. The use of ``len(emails)`` calls ``QuerySet.__len__()``, reusing the result
cache.

#. The :ttag:`for` loop iterates over the already filled cache.
#. The ``for`` loop iterates over the already filled cache.

In total, this code does either one or zero database queries. The only
deliberate optimization performed is the use of the :ttag:`with` tag. Using
``QuerySet.exists()`` or ``QuerySet.count()`` at any point would cause
additional queries.
deliberate optimization performed is using the ``emails`` variable. Using
``QuerySet.exists()`` for the ``if`` or ``QuerySet.count()`` for the count
would each cause additional queries.

Use ``QuerySet.update()`` and ``delete()``
------------------------------------------
Expand Down