Count more than one occurrence in a Django query - django

I have two models: Players and Tournaments.
Players have also a type of game they play (dota, lol, magic, etc). They can participate in many tournaments at the same time (only once per Tournament). To manage the inscriptions, I use another model called TournamentMatch, that creates a new object for every inscription with the ID of the tournament and the player.
class Player(models.Model):
name = models.CharField(_('name'), max_length=150, null=True, blank=True)
email = models.EmailField(_('email address'), unique=True)
is_dota_player = models.BooleanField(default=False)
is_lol_player = models.BooleanField(default=False)
is_magic_player = models.BooleanField(default=False)
class Tournament(models.Model):
name = models.CharField(max_length=200)
date_start = models.DateField()
date_end = models.DateField()
class TournamentMatch(models.Model):
tournament = models.ForeignKey(Tournament)
player = models.ForeignKey(Player)
date_added = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
I want to count the number of Players that have more than two inscription to tournaments and are, for example, dota players.
I can easily achieve this with a for loop for every player, but for perfomance reasons, I want to achieve this using a Django query.
For example, if I want to count the dota players that have one or more inscription, I will do:
TournamentMatch.objects.filter(
player__is_dota_player=True
).distinct(
'player'
).count()
I'm sure is possible but I don't know how to count every ocurrence of distinct players on tournaments and only filter those that have more than one (and not only one).
I will appreciate any help or pointers!

# subquery for counting match per player (note OuterRef which will be linked to the outer query row)
player_match_count_subquery = TournamentMatch.objects.filter(player_id=OuterRef('pk'), ).\
annotate(match_count=Count('pk'), ).values('match_count', )[:1]
# the main (outer) query which uses subquery output for filtering
Players.objects.filter(is_dota_player=True, ).\
annotate(match_count=Subquery(player_match_count_subquery), ).\
filter(match_count__gt=2, )
https://docs.djangoproject.com/en/2.2/ref/models/expressions/#subquery-expressions
this code will produce logically this SQL query:
SELECT p.*, tm.match_count
FROM players p
CROSS APPLY (
SELECT COUNT(id) match_count
FROM tournamentmatch tm
WHERE tm.player_id = p.id
) tm
WHERE p.is_dota_player = $true
AND tm.match_count > 2

Related

How to inner join tables based on ManyToManyField and group by a parameter and get latest one in Django?

I have two models with ManyToManyField relationship:
class Education(models.Model):
title = models.CharField(default=None, max_length=100)
content = models.TextField(default=None)
price = models.ManyToManyField(Price)
class Price(models.Model):
cost = models.CharField(default=None, max_length=20)
created_at = models.DateTimeField(auto_now=True, null=True, blank=True)
I can fetch all rows like this:
result = Education.objects.filter(price__in=Price.objects.all()).select_related('Price')/
.values_list('title', 'content', 'price__cost', 'price__created_at')
But now i want to group by education.id and the cost parameter should be latest parameter that inserted(based on created_at).
So i want to have list of all Education with latest cost that inserted for every education.
Will it work for you, It will return the respective id
Education.objects.filter(price__in=Price.objects.all()).select_related('Price').values('id').annotate(price_id=Max('price__id'))

Django queryset filtering out objects with common members

Im creating 2-step form. In first part captain is choosing one of his teams, in second i'm retrieving chosen team from first step and creating queryset based on it. Second form should only filter teams that doesn't have players in common with team from first step. Each team has only 3 players.
class Team(models.Model):
name = models.CharField(max_length=200, null=True, unique=True)
captain = models.ForeignKey(Player, on_delete=models.CASCADE, related_name="captain")
team_creation_date = models.DateTimeField(auto_now_add=True, null=True)
players = models.ManyToManyField(Player, through="PlayerTeam", related_name="players")
class PlayerTeam(models.Model):
player = models.ForeignKey(Player,on_delete=models.CASCADE)
team = models.ForeignKey(Team,on_delete=models.CASCADE)
join_date = models.DateField(auto_now_add=True)
One solution without using Q is just to exclude the teams that that have common players.
selected_team = Team.objects.get(...) # get selected team
not_allowed_players = selected.team.players.all()
teams = Team.objects.all().exclude(players__in=not_allowed_players)

How to use Django annotate?

I'd like to ask, how I could shrink this to one command? I understand that annotate is proper way to do this,but don't understand how.
Here is my code, which is too slow:
sum = 0
for contact in self.contacts.all():
sum += (contact.orders.aggregate(models.Sum('total'))['total__sum'])
return sum
I'd like to get Sum for each contact, all records in total column of relevant orders.
Code above produces sum, but is sluggishly slow. It is my understand it can be done with annotate,but not sure how to use it.
Here is Contact:
class Contact(models.Model):
company = models.ForeignKey(
Company, related_name="contacts", on_delete=models.PROTECT)
first_name = models.CharField(max_length=80)
last_name = models.CharField(max_length=80, blank=True)
email = models.EmailField()
And here is Orders:
class Order(models.Model):
order_number = models.CharField(max_length=80)
company = models.ForeignKey(Company, related_name="orders")
contact = models.ForeignKey(Contact, related_name="orders")
total = models.DecimalField(max_digits=12, decimal_places=6)
order_date = models.DateTimeField(null=True, blank=True)
Help please
You can annotate your queryset on the Contract model with:
from django.db.models import Sum
Contract.objects.annotate(
total_orders=Sum('orders__total')
)
The Contract objects that arise from this queryset will have an extra attribute .total_orders that contains the sum of the total field of the related Order objects.
This will thus create a query that looks like:
SELECT contract.*, SUM(order.total)
FROM contract
LEFT OUTER JOIN order ON order.contract_id = contract.id
GROUP BY contract.id

Complex django annotation

I have the following models:-
class Group(models.Model):
name = models.CharField(max_length=200)
class Game(models.Model):
group = models.ForeignKey(Group, related_name='games')
date = models.DateField()
players = models.ManyToManyField(
Player, through='GameMembership', related_name='games'
)
class GameMembership(models.Model):
game = models.ForeignKey(Game, related_name='memberships')
player = models.ForeignKey(Player, related_name='memberships')
selected = models.BooleanField(default=False)
injured = models.BooleanField(default=False)
class Player(models.Model):
group = models.ForeignKey('groups.Group', related_name='players')
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='players')
I want to annotate all of the players in the group with a score which is calculated as follows:-
For the last 10 games for which a player wasn't injured, score 5 if they were selected.
I can do this using Sum/Case/When if I ignore the "wasn't injured" clause, by using a manager method on Player which looks something like this:-
def with_availability_scores(self, group):
for_games = group.games.reverse()[:10]
return self.annotate(
availability_score=Sum(Case(
When(
memberships__selected=True, memberships__game__in=for_games,
then=5)
default=0, output_field=IntegerField()))
)
But the addition of the "injured" clause means that I can't use that for_games variable like that to begin with.
I suspect it can be done using Subquery and OuterRef but I can't quite figure out the exact syntax I need.
Any ideas?

Can't figure out join statement in Django

I'm trying to figure out how to execute the following sql join statement in Django without resorting to just raw sql. Is there a way to do it?
Select * from playertable, seasontable where player.id = season.player_id
Here are my models. Just to clarify, I used abbreviated table names in the above query for clarify
class Player(models.Model):
name = models.CharField(max_length=200)
team = models.CharField(max_length=3)
position = models.CharField(max_length=3)
class PlayerSeason(models.Model):
player = models.ForeignKey(Player)
year = models.IntegerField()
games_played = models.IntegerField()
goals = models.IntegerField()
assists = models.IntegerField()
points = models.IntegerField()
plusminus = models.CharField(max_length=200)
pim = models.IntegerField()
ppg = models.IntegerField()
shg = models.IntegerField()
gwg = models.IntegerField()
otg = models.IntegerField()
shots = models.IntegerField()
shooting_percentage = models.DecimalField(max_digits=5, decimal_places=2)
toi = models.CharField(max_length=200)
sftg = models.DecimalField(max_digits=5, decimal_places=2)
face_off = models.DecimalField(max_digits=5, decimal_places=2)
How should I do this with a Django QuerySet?
If all you wanted to do was to get all the players associated with a given season you could make use of Django's backwards relationships
When you use a ForeignKeyField to a model, in this case Season, the that model instances get an attribute which allows you to get a queryset of all the related objects.
In your example you could use season.player_set.all().
You can pass an optional parameter related_name to the ForeignKeyField that allows you to change the name of the season attribute.
Is there a way to do it?
No. Django's ORM deals with one model at a time, and you are getting columns from two tables. Perform a query on either of the models and then access the appropriate field to get the related model.