Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django proper use of select_related or prefetch_related on a ForeignKey

I'm trying to figure out how to use select_related or prefetch_related to optimize a query from the other end of a foreign key. For example:

Say I have some models such as the following:

class Product(models.Model):
    name = models.CharField(max_length=50)

class ProductImage(models.Model):
    image = models.ImageField(upload_to="images")
    product = models.ForeignKey("myapp.Product", related_name="product_images")

If I want to print all of the ProductImage, I have to iterate through all of the products and then, for each product, iterate through all of the product images. However, this produces O(n * m) database queries.

for product in Product.objects.all():
    print product.product_images.all()

I'm wondering if there is a way to utilize select_related or prefetch_related to reduce this lookup to one or two queries.

As documented by Django, I can get select_related to work when I select ProductImage. As you can see, adding the select_related creates a JOIN to the product table.

>>> ProductImage.objects.all().query.sql_with_params()
(u'SELECT "myapp_productimage"."id",  ...  FROM "myapp_productimage" .... 

>>> ProductImage.objects.select_related('product').query.sql_with_params()
(u'SELECT "myapp_productimage"."id", ...   FROM "myapp_productimage" LEFT OUTER JOIN ....

However, how do I accomplish the reverse? For example, how do I query all of the Products and have it JOIN on the ProductImages? Doing something like the following doesn't seem to work.

Product.objects.select_related('product_images').query.sql_with_params()

Is something like this possible in Django?

Thanks for your consideration.

like image 877
Joe J Avatar asked Mar 29 '14 17:03

Joe J


1 Answers

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.

like image 50
Daniel Roseman Avatar answered Sep 22 '22 05:09

Daniel Roseman