I'm trying to bulk_create
or bulk_update
instances of a model Matchup
, where two of its fields are dependent on the equality or lack thereof of two of its related fields' reverse relationships.
The Matchup
has both a home_team
and away_team
, both of which are ForeignKey
fields. There's are also is_divisional
and is_conference
fields to denote whether the matchup is between teams in the same division or conference.
class Matchup(models.Model):
home_team = models.ForeignKey(
"teams.Team",
on_delete=models.CASCADE,
related_name="home_matchups",
)
away_team = models.ForeignKey(
"teams.Team",
on_delete=models.CASCADE,
related_name="away_matchups",
)
is_divisional = models.BooleanField(default=False)
is_conference = models.BooleanField(default=False)
The Team
model also has two ForeignKey
fields, conference
and division
.
class Team(models.Model):
conference = models.ForeignKey(
"leagues.Conference",
on_delete=models.CASCADE,
related_name="teams",
)
division = models.ForeignKey(
"leagues.Division",
on_delete=models.CASCADE,
related_name="teams",
)
So the goal here is to check whether the Matchup.home_team
and Matchup.away_team
belong to the same conference or division. If so, is_conference
/is_divisional
respectively should be True
.
Here's what I initially had. It works, but leads to hundreds of duplicate queries. I perform this bulk_update
after all the objects are created in bulk.
for matchup in matchup_objs:
if matchup.home_team.division == matchup.away_team.division:
matchup.is_divisional = True
if matchup.home_team.conference == matchup.away_team.conference:
matchup.is_conference = True
Matchup.objects.bulk_update(matchup_objs, ["is_divisional", "is_conference"])
In an attempt to reduce the duplicate queries, I tried using a Case()
expression during the bulk_create
of the objects like so.
matchup_objs = Matchup.objects.bulk_create(
[
Matchup(
home_team=home_team,
away_team=away_team,
is_conference=Case(
When(home_team__conference=F('away_team__conference'), then=Value(True)),
default=Value(False)
),
is_divisional=Case(
When(home_team__division=F('away_team__conference'), then=Value(True)),
default=Value(False)
),
)
for matchup in matchups
]
)
But I'm getting a FieldError: Joined field references are not permitted in this query
. I believe this is because joins aren't allowed for a bulk_create
operation.
I thought about using a SubQuery()
here based on this answer but I'm not sure how to accomplish that as the When()
statement expects a parameter name and the home_team__conference
lookup is what's throwing the FieldError.
Is there a way to accomplish this or do I need to resort to raw SQL here?
CodePudding user response:
if the related attributes are not cached django will hit the database again and again for every attribute you access in the loop
for matchup in matchup_objs:
if matchup.home_team.division == matchup.away_team.division:
matchup.is_divisional = True
if matchup.home_team.conference == matchup.away_team.conference:
matchup.is_conference = True
Matchup.objects.filter(foo=foo).select_related('home_team__division', 'home_team__conference', 'away_team__division', 'away_team__conference'