Ludovico Verniani
Ludovico Verniani

Reputation: 470

Is accessing foreign key ID field directly faster in Django?

Please reference these example models:

class Player(models.Model):
    team = models.ForeignKey(Team, on_delete=models.CASCADE)

class Team(models.Model):
    captain = models.ForeignKey(Manager, on_delete=models.CASCADE)
    country = models.CharField()

class Manager(models.Model):
    networth = models.FloatField()

I am trying to figure out if the following is faster than the alternative (ie. accesses the database less):

team = Team.objects.get()
Player.objects.filter(team_id=team.id)

Alternative:

team = Team.objects.get()
Player.objects.filter(team=team)

Upvotes: 1

Views: 1178

Answers (2)

user1600649
user1600649

Reputation:

So to illustrate my point in the comments better - what I see often in the wild is url structures like this:

/teams/<int:pk>/players/

Accompanied by view code like this:

def players_view(request, pk):
    team = Team.objects.get(pk=pk)
    context = {
        "players": Player.objects.filter(team=team)
    }
    ...

while you can just do:

def players_view(request, pk):
    context = {
        "players": Player.objects.filter(team__pk=pk)
    }
    ...

And extra points for:

def players_view(request, pk):
    context = {
        "players": Player.objects.filter(team__pk=pk).select_related("team")
    }
    ...

And the template trick:

{% for player in players %}
    <!-- team name only once -->
    {% if forloop.first %}
        <h1>Players of {{player.team.name}}</h1>
    {% endif %}
    <!-- data of player here -->
{% endfor %}

Upvotes: 1

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477641

I am trying to figure out if the following is faster than the alternative

No. It will result in the same query. Except for a few cycles that Django spends on inspecting what you passed and accessing the primary key, it does not make any difference.

You can inspect this in The Django shell with:

>>> print(Player.objects.filter(team_id=team.id).query)
SELECT `app_name_player`.`id`, `app_name_player`.`team_id` FROM `app_name_player` WHERE `app_name_player`.`team_id` = 1
>>> print(Player.objects.filter(team=team).query)
SELECT `app_name_player`.`id`, `app_name_player`.`team_id` FROM `app_name_player` WHERE `app_name_player`.`team_id` = 1

So the two queries are identical. Probably it is more idiomatic to fetch these with:

team.player_set.all()

If you need to access the Players of a set of Teams, you can make use of .prefetch_related(…) [Django-doc] to fetch all the Players for a set of teams in one query, avoiding the N+1 problem:

teams = Team.objects.prefetch_related('player_set')

Here if you then iterate over the teams, and fetch the player_set of each Team object, it will not make extra queries, since it has already fetched all the related Players in one extra query, and did the "joining" at the Python/Django layer.

Upvotes: 2

Related Questions