What's the difference between select_related and prefetch_related in Django ORM?
Your understanding is mostly correct. You use select_related
when the object that you're going to be selecting is a single object, so OneToOneField
or a ForeignKey
. You use prefetch_related
when you're going to get a "set" of things, so ManyToManyField
s as you stated or reverse ForeignKey
s. Just to clarify what I mean by "reverse ForeignKey
s" here's an example:
class ModelA(models.Model):
pass
class ModelB(models.Model):
a = ForeignKey(ModelA)
ModelB.objects.select_related('a').all() # Forward ForeignKey relationship
ModelA.objects.prefetch_related('modelb_set').all() # Reverse ForeignKey relationship
The difference is that select_related
does an SQL join and therefore gets the results back as part of the table from the SQL server. prefetch_related
on the other hand executes another query and therefore reduces the redundant columns in the original object (ModelA
in the above example). You may use prefetch_related
for anything that you can use select_related
for.
The tradeoffs are that prefetch_related
has to create and send a list of IDs to select back to the server, this can take a while. I'm not sure if there's a nice way of doing this in a transaction, but my understanding is that Django always just sends a list and says SELECT ... WHERE pk IN (...,...,...) basically. In this case if the prefetched data is sparse (let's say U.S. State objects linked to people's addresses) this can be very good, however if it's closer to one-to-one, this can waste a lot of communications. If in doubt, try both and see which performs better.
Everything discussed above is basically about the communications with the database. On the Python side however prefetch_related
has the extra benefit that a single object is used to represent each object in the database. With select_related
duplicate objects will be created in Python for each "parent" object. Since objects in Python have a decent bit of memory overhead this can also be a consideration.
Django proper use of select_related or prefetch_related on a ForeignKey
That is exactly what prefetch_related
does.
Product.objects.prefetch_related('product_images')
However, there's no point in using query.sql_with_params()
to diagnose this: prefetch_related
does two queries, and the second one won't show up there. You should use django.db.connection.queries
to examine the queries that Django is making, or even better use the Django debug toolbar to show you.
Django prefetch and select related
In your specific case I suppose better to use annotation instead of prefetch:
from django.db.models import Count, Q
cities = City.objects
.annotate(bank_count=Count("street", filter=Q(street__building_id=1)))
.annotate(cinema_count=Count("street", filter=Q(street__building_id=2)))
.annotate(church_count=Count("street", filter=Q(street__building_id=3)))
Now you can directly use bank_count
, cinema_count
and church_count
attributes:
for city in cities:
print(city.bank_count)
print(city.cinema_count)
print(city.church_count)
In case you want to use prefetch_related
you need to use Prefetch
object. This allows you tof filter prefetched objects:
City.objects.prefect_related(
Prefetch("street_set", queryset=Street.objects.filter(building_id=1), to_attr='has_bank'),
Prefetch("street_set", queryset=Street.objects.filter(building_id=2), to_attr='has_cinema'),
Prefetch("street_set", queryset=Street.objects.filter(building_id=3), to_attr='has_church')
)
Note to_attr
argument this helps you to prefetch same model's objects with different filters to different attributes. So you can do now:
for city in cities:
print(city.has_bank)
print(city.has_cinema)
print(city.has_church)
Does it matter in which order you use prefetch_related and filter in Django?
It doesn't matter where you specify prefetch_related
as long as it's before any records are fetched. Personally I put things like prefetch_related
, select_related
. and only
at the end of the chain but that just feels more expressive to me from a code readability perspective.
But that is not true of all manager methods. Some methods do have different effects depending on their position in the chain, for example order_by
can have positional significance when used with distinct
(group by).
django model select_related or prefetch_related child model
To customize how a queryset is fetched in a generic view one needs to override the get_queryset
method of the view. Also if one wants to use select_related
on a prefetched object one should use Prefetch
objects [Django docs] and specify their queryset
. Since this is a DetailView
and hence deals with one object you will need to override get_object
instead:
from django.db.models import Prefetch
from django.http import Http404
from django.utils.translation import gettext as _
class ReceipeDetailView(PermissionRequiredMixin, DetailView):
permission_required = 'item_management.view_receipemaster'
model = ReceipeMaster
template_name = 'receipes/show.html'
context_object_name = 'receipe'
def get_object(self, queryset=None):
# Use a custom queryset if provided; this is required for subclasses
# like DateDetailView
if queryset is None:
queryset = self.get_queryset()
# Next, try looking up by primary key.
pk = self.kwargs.get(self.pk_url_kwarg)
slug = self.kwargs.get(self.slug_url_kwarg)
if pk is not None:
queryset = queryset.filter(pk=pk)
# Next, try looking up by slug.
if slug is not None and (pk is None or self.query_pk_and_slug):
slug_field = self.get_slug_field()
queryset = queryset.filter(**{slug_field: slug})
# If none of those are defined, it's an error.
if pk is None and slug is None:
raise AttributeError(
"Generic detail view %s must be called with either an object "
"pk or a slug in the URLconf." % self.__class__.__name__
)
queryset = queryset.select_related('item').prefetch_related(
Prefetch(
'items',
queryset=ReceipeDetail.objects.select_related('item_type', 'item')
)
)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
select_related and prefetch_related django
- use
select_related(fieldname)
to load objects referenced directly from this model by a foreign key (many2one) - use
prefetch_related(fieldname)
to load objects referenced indirectly (object that have references to this model)- by one2many (that means, this object is referenced from another model by some foreign key)
- by many2many (that's your case - both
Student
andSubject
are referenced from an intermediate technical model)
qs = Student.objects.select_related(
'student', 'section', 'profile',
).prefetch_related(
'enrolled_subjects',
)
How to add filters on a related field - just use filter as usual, but prefix names of fields on a related model with a name of referrer field.
Example:
Let's say, we need to get such Student
s, that they have such Section
s, that their section
is 'example'.
How do we do that in python:
Student.objects.filter(section__section='example')
# (1)->^^^^^^^ ^^^^^^^<-(2)
- the name of field on the
Student
model - the name of field on the
Section
model
You're free combine prefetches, selects and filters in any order.
Student.objects.filter(...).prefetch_related(...).select_related(...).filter(...)
Django prefetch_related and select_related
From the FineManual(tm) (emphasis is mine):
There may be some situations where you wish to call select_related()
with a lot of related objects, or where you don’t know all of the
relations. In these cases it is possible to call select_related() with
no arguments. This will follow all non-null foreign keys it can find -
nullable foreign keys must be specified. This is not recommended in
most cases as it is likely to make the underlying query more complex,
and return more data, than is actually needed.
Related Topics
Parsing Boolean Values with Argparse
How to Get Different Colored Lines for Different Plots in a Single Figure
How to Get a List of All the Duplicate Items Using Pandas in Python
Pandas: Drop a Level from a Multi-Level Column Index
Why Isn't My Pandas 'Apply' Function Referencing Multiple Columns Working
Scraping: Ssl: Certificate_Verify_Failed Error for Http://En.Wikipedia.Org
Case Insensitive Regular Expression Without Re.Compile
Django Set Default Form Values
How to Activate an Anaconda Environment
Quick and Easy File Dialog in Python
Create a .CSV File with Values from a Python List
Pandas: Rolling Mean by Time Interval
Matplotlib: Format Axis Offset-Values to Whole Numbers or Specific Number
Python Glob Multiple Filetypes
Regular Expression Matching a Multiline Block of Text
Typeerror: Unhashable Type: 'Dict'