How to merge two queryset based on common key name in django
Question:
Let’s say I have 2 QuerySet:
<Queryset [a, b, c]>
and <Queryset [a, d, e]>
How can I merge those two to achieve:
<Queryset [a, b, c, d, e]>
Edit with real case:
qs_home = Match.objects.filter(competition=competition)
.values(name=F("home_team__name"))
.annotate(home_win=Sum(Case(When(home_score__gt=F('away_score'), then=1))))
.annotate(home_draw=Sum(Case(When(home_score=F('away_score'), then=1))))
.annotate(home_lose=Sum(Case(When(home_score__lt=F('away_score'), then=1))))
.annotate(home_goal=Sum('home_score'))
.annotate(home_conceded=Sum('away_score'))
.annotate(home_point=Sum(Case(
When(home_score__gt=F('away_score'), then=3),
When(home_score=F('away_score'), then=1)
)))
.order_by('home_team__name')
qs_away = Match.objects.filter(competition=competition)
.values(name=F("away_team__name"))
.annotate(away_win=Sum(Case(When(away_score__gt=F('home_score'), then=1))))
.annotate(away_draw=Sum(Case(When(away_score=F('home_score'), then=1))))
.annotate(away_lose=Sum(Case(When(away_score__lt=F('home_score'), then=1))))
.annotate(away_goal=Sum('away_score'))
.annotate(away_conceded=Sum('home_score'))
.annotate(away_point=Sum(Case(
When(away_score__gt=F('home_score'), then=3),
When(away_score=F('home_score'), then=1)
)))
.order_by('away_team__name')
If i do qs_home.union(qs_away)
the qs_away
just append at the end of qs_home
with the key from qs_home
but the value of qs_away
Edit : My Workaround
for qs in qs_home:
away_dict = next((item for item in qs_away if item['name'] == qs['name']), None)
qs['away_win'] = away_dict['away_win']
qs['away_draw'] = away_dict['away_draw']
qs['away_lose'] = away_dict['away_lose']
qs['away_goal'] = away_dict['away_goal']
qs['away_conceded'] = away_dict['away_conceded']
qs['away_point'] = away_dict['away_point']
this will append all the away_
key and value to qs_home
. But of course im trying to avoid for loop if possible.
if im using union()
, lets say, the len of qs_home
and qs_away
both 18. I get the queryset with 36 length. I want the length of the new queryset just 18 with the key, value append to each dict with the same name
Updated with my model:
class Match(models.Model):
competition = models.ForeignKey(Competition, related_name='matches', on_delete=models.CASCADE)
gameweek = models.PositiveSmallIntegerField(blank=True, null=True)
home_team = models.ForeignKey(Team, related_name='home_matches', on_delete=models.CASCADE)
away_team = models.ForeignKey(Team, related_name='away_matches', on_delete=models.CASCADE)
home_score = models.PositiveSmallIntegerField(default=0)
away_score = models.PositiveSmallIntegerField(default=0)
STATUS = Choices(
(1, 'not_started', 'Not Started'),
(2, 'half_time', 'Half Time'),
(3, 'full_time', 'Full Time'),
(9, 'postponed', 'Postponed')
)
status = models.PositiveSmallIntegerField(choices=STATUS, default=STATUS.not_started)
Answers:
You can use union()
and by looking at your queryset example, it also seems you want unique values, so try the following:
q1 = Model.objects.filter(name__in=['a', 'b', 'c'])
q2 = Model.objects.filter(name__in=['a', 'd', 'e'])
combined_qs = q1.union(q2)
This will return a new queryset that contains all distinct elements from both q1
and q2
In your case, it will be:
<QuerySet [a, b, c, d, e]>
Edit
You can make the loop more concise by using Python’s built-in zip()
function to iterate through the two QuerySets together and updating the first one with the values from the second one, like so following:
qs_home = Match.objects.filter(competition=competition)
.values(name=F("home_team__name"))
.annotate(home_win=Sum(Case(When(home_score__gt=F('away_score'), then=1))))
.annotate(home_draw=Sum(Case(When(home_score=F('away_score'), then=1))))
.annotate(home_lose=Sum(Case(When(home_score__lt=F('away_score'), then=1))))
.annotate(home_goal=Sum('home_score'))
.annotate(home_conceded=Sum('away_score'))
.annotate(home_point=Sum(Case(
When(home_score__gt=F('away_score'), then=3),
When(home_score=F('away_score'), then=1)
)))
.order_by('home_team__name')
qs_away = Match.objects.filter(competition=competition)
.values(name=F("away_team__name"))
.annotate(away_win=Sum(Case(When(away_score__gt=F('home_score'), then=1))))
.annotate(away_draw=Sum(Case(When(away_score=F('home_score'), then=1))))
.annotate(away_lose=Sum(Case(When(away_score__lt=F('home_score'), then=1))))
.annotate(away_goal=Sum('away_score'))
.annotate(away_conceded=Sum('home_score'))
.annotate(away_point=Sum(Case(
When(away_score__gt=F('home_score'), then=3),
When(away_score=F('home_score'), then=1)
)))
.order_by('away_team__name')
for home_dict, away_dict in zip(qs_home, qs_away):
home_dict.update(away_dict)
merged_qs = qs_home
Let’s say I have 2 QuerySet:
<Queryset [a, b, c]>
and <Queryset [a, d, e]>
How can I merge those two to achieve:
<Queryset [a, b, c, d, e]>
Edit with real case:
qs_home = Match.objects.filter(competition=competition)
.values(name=F("home_team__name"))
.annotate(home_win=Sum(Case(When(home_score__gt=F('away_score'), then=1))))
.annotate(home_draw=Sum(Case(When(home_score=F('away_score'), then=1))))
.annotate(home_lose=Sum(Case(When(home_score__lt=F('away_score'), then=1))))
.annotate(home_goal=Sum('home_score'))
.annotate(home_conceded=Sum('away_score'))
.annotate(home_point=Sum(Case(
When(home_score__gt=F('away_score'), then=3),
When(home_score=F('away_score'), then=1)
)))
.order_by('home_team__name')
qs_away = Match.objects.filter(competition=competition)
.values(name=F("away_team__name"))
.annotate(away_win=Sum(Case(When(away_score__gt=F('home_score'), then=1))))
.annotate(away_draw=Sum(Case(When(away_score=F('home_score'), then=1))))
.annotate(away_lose=Sum(Case(When(away_score__lt=F('home_score'), then=1))))
.annotate(away_goal=Sum('away_score'))
.annotate(away_conceded=Sum('home_score'))
.annotate(away_point=Sum(Case(
When(away_score__gt=F('home_score'), then=3),
When(away_score=F('home_score'), then=1)
)))
.order_by('away_team__name')
If i do qs_home.union(qs_away)
the qs_away
just append at the end of qs_home
with the key from qs_home
but the value of qs_away
Edit : My Workaround
for qs in qs_home:
away_dict = next((item for item in qs_away if item['name'] == qs['name']), None)
qs['away_win'] = away_dict['away_win']
qs['away_draw'] = away_dict['away_draw']
qs['away_lose'] = away_dict['away_lose']
qs['away_goal'] = away_dict['away_goal']
qs['away_conceded'] = away_dict['away_conceded']
qs['away_point'] = away_dict['away_point']
this will append all the away_
key and value to qs_home
. But of course im trying to avoid for loop if possible.
if im using union()
, lets say, the len of qs_home
and qs_away
both 18. I get the queryset with 36 length. I want the length of the new queryset just 18 with the key, value append to each dict with the same name
Updated with my model:
class Match(models.Model):
competition = models.ForeignKey(Competition, related_name='matches', on_delete=models.CASCADE)
gameweek = models.PositiveSmallIntegerField(blank=True, null=True)
home_team = models.ForeignKey(Team, related_name='home_matches', on_delete=models.CASCADE)
away_team = models.ForeignKey(Team, related_name='away_matches', on_delete=models.CASCADE)
home_score = models.PositiveSmallIntegerField(default=0)
away_score = models.PositiveSmallIntegerField(default=0)
STATUS = Choices(
(1, 'not_started', 'Not Started'),
(2, 'half_time', 'Half Time'),
(3, 'full_time', 'Full Time'),
(9, 'postponed', 'Postponed')
)
status = models.PositiveSmallIntegerField(choices=STATUS, default=STATUS.not_started)
You can use union()
and by looking at your queryset example, it also seems you want unique values, so try the following:
q1 = Model.objects.filter(name__in=['a', 'b', 'c'])
q2 = Model.objects.filter(name__in=['a', 'd', 'e'])
combined_qs = q1.union(q2)
This will return a new queryset that contains all distinct elements from both q1
and q2
In your case, it will be:
<QuerySet [a, b, c, d, e]>
Edit
You can make the loop more concise by using Python’s built-in zip()
function to iterate through the two QuerySets together and updating the first one with the values from the second one, like so following:
qs_home = Match.objects.filter(competition=competition)
.values(name=F("home_team__name"))
.annotate(home_win=Sum(Case(When(home_score__gt=F('away_score'), then=1))))
.annotate(home_draw=Sum(Case(When(home_score=F('away_score'), then=1))))
.annotate(home_lose=Sum(Case(When(home_score__lt=F('away_score'), then=1))))
.annotate(home_goal=Sum('home_score'))
.annotate(home_conceded=Sum('away_score'))
.annotate(home_point=Sum(Case(
When(home_score__gt=F('away_score'), then=3),
When(home_score=F('away_score'), then=1)
)))
.order_by('home_team__name')
qs_away = Match.objects.filter(competition=competition)
.values(name=F("away_team__name"))
.annotate(away_win=Sum(Case(When(away_score__gt=F('home_score'), then=1))))
.annotate(away_draw=Sum(Case(When(away_score=F('home_score'), then=1))))
.annotate(away_lose=Sum(Case(When(away_score__lt=F('home_score'), then=1))))
.annotate(away_goal=Sum('away_score'))
.annotate(away_conceded=Sum('home_score'))
.annotate(away_point=Sum(Case(
When(away_score__gt=F('home_score'), then=3),
When(away_score=F('home_score'), then=1)
)))
.order_by('away_team__name')
for home_dict, away_dict in zip(qs_home, qs_away):
home_dict.update(away_dict)
merged_qs = qs_home