Andrew
Andrew

Reputation: 3185

Django select related in raw request

How to make "manual" select_related imitation to avoid undesirable DB hits?

we have:

class Country:
    name = CharField()
class City:
    country = models.ForeignKey(Country)
    name = models.CharField()

cities = City.objects.raw("select * from city inner join country on city.country_id = country.id where name = 'london'")

#this will hill hit DB
print cities[0].country.name

How to tell django that related models are already fetched.

Upvotes: 16

Views: 6960

Answers (3)

Todor
Todor

Reputation: 16010

A solution with prefetch_related (this means that two queries will be made, 1 for the cities and 1 for the countries) taken from django-users which is not part of the public API but is working on Django 1.7

from django.db.models.query import prefetch_related_objects
#raw querysets do not have len()
#thats why we need to evaluate them to list
cities = list(City.objects.raw("select * from city inner join country on city.country_id = country.id where name = 'london'"))
prefetch_related_objects(cities, ['country'])

UPDATE

Now in Django 1.10 prefetch_related_objects is part of the public API.

Upvotes: 15

willy
willy

Reputation: 1490

Not sure if you still need this, but I solved it starting with Alasdair's answer. You want to use the info from the query to build the model or it'll still fire additional queries when you try to access the foreign key field. So in your case, you'd want:

    cities = list(City.objects.raw("""
        SELECT
            city.*, country.name as countryName
        FROM
            cities INNER JOIN country ON city.country_id = country.id
        WHERE
            city.name = 'LONDON"""))
    for city in cities:
        city.country = Country(name=city.countryName)

The line that assigns the country doesn't hit the database, it's just creating a model. Then after that, when you access city.country it won't fire another database query.

Upvotes: 10

Alasdair
Alasdair

Reputation: 308889

I'm not sure if you can do this. As an alternative, you can select individual fields from the country table and access them on each instance.

cities = City.objects.raw("select city.*, name as country_name from city inner join country on city.country_id = country.id where name = 'london'")

city = cities[0]
# this will not hit the database again
city.country_name

Upvotes: 3

Related Questions