Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I count across several relationships in django

For a small project I have a registry of matches and results. Every match is between teams (could be a single player team), and has a winner. So I have Match and Team models, joined by a MatchTeam model. This looks like so (simplified)see below for notes

class Team(models.Model):
    ...

class Match(models.Model):
    teams = ManyToManyField(Team, through='MatchTeam')
    ...

class MatchTeam(models.Model):
    match = models.ForeignKey(Match, related_name='matchteams',)
    team = models.ForeignKey(Team)
    winner = models.NullBooleanField()
    ...

Now I want to do some stats on the matches, starting with looking up who is the person that beats you the most. I'm not completely sure how to do this, at least, not efficiently.

In SQL (just approximating here), I would mean something like this:

SELECT their_matchteam.id, COUNT(*) as cnt
FROM matchteam AS your_mt  
JOIN matchteam AS their_mt ON your_mt.match_id = their_mt.match_id
WHERE your.matchteam.id IN <<:your teams>> 
  your_matchteam.winner = false
GROUP BY their_matchteam.team_id
ORDER BY cnt DESC

(this also needs a "their_mt is not your_mt" clause btw, but the concept is clear, right?)

While I have not tested this as SQL, it's just to give an insight to what I'm looking for: I want to find this result via a Django aggregation.

According to the manual I can annotate results with an aggregation, in this case a Count. Joining MatchTeams straight on MatchTeams as I'm doing in the SQL is a bit of a shortcut maybe, as there 'should' be a Match in between? At least, I wouldn't know how to translate that into Django

So maybe I need to find certain matches for my team, and then annotate them with the count of the other team? But what is 'the other team'?

Quick write-up would look like:

nemesis = Match.objects \
        .filter(matchteams__in=yourteams) \
        .annotate(cnt=Count('<<otherteam>>')).order_by('-cnt')[0]

If this is the right track, how should I define the Count here. And if it's not the right track, what is?

As is, this is all about teams instead of users. This is just to keep things simple :)

An additional question might be: should I even do this with that Django ORM stuff, or am I better off just adding SQL? That has the obvious disadvantage that you're stuck with writing very generic code (is this even possible?) or fixing your DB-backend. If not needed, I'd like to avoid that.


About the model: I really want to understand what I can change about the model to make it better, but I can't really see a solution without downsides. Let me try to explain:

I want to support matches with arbitrary amount of teams, so for instance a 5-team-match. This means I have many-to-many relationship and not one that is for instance 1 match to 2 teams. If that was the case, you could denormalize and put the winners/scores in the team table. But this is not the case.

Extra data about the results of one team (e.g. their final score, their time) is by definition a property of the relation. It cannot go into the team table (as it would be per match and you can have an undefined amount of matches), and it cannot go in the match table for the same reason mutatis mutandis.

Example: I have teams A,B,C,D and E playing a match. Team A and Team B have 10 points, the rest all have 0 points. I want to save the amount of points, and that Team A and Team B are the winners of this match.

So to the comments suggesting I need a 'better' design, by all means, if you have one I would gladly see it, but if you want to support what I support, it's going to be hard.

And as a final remark: This data can be easilly retrieved in SQL, so the model seems fine to me: I'm just too much of a beginner in Django to be able to do it in Django's ORM!

like image 776
Nanne Avatar asked Apr 17 '17 09:04

Nanne


People also ask

How does Django implement one to many relationship?

To handle One-To-Many relationships in Django you need to use ForeignKey . The current structure in your example allows each Dude to have one number, and each number to belong to multiple Dudes (same with Business).

How do you count in Django ORM?

Use Django's count() QuerySet method — simply append count() to the end of the appropriate QuerySet. Generate an aggregate over the QuerySet — Aggregation is when you "retrieve values that are derived by summarizing or aggregating a collection of objects." Ref: Django Aggregation Documentation.

How do you add data to many-to-many fields in Django?

The add() function allows us to add an object to this ManyToManyField. And this is how to add an object to a ManyToManyField in Django.

How do I create a one to many relationship in Django?

To define a one to many relationship in Django models you use the ForeignKey data type on the model that has the many records (e.g. on the Item model). Listing 7-22 illustrates a sample of a one to many Django relationship.

How do you get the reverse of a relationship in Django?

When you use relationship model data types, Django automatically establishes the reverse relationship between data types with the the _set reference. This mechanism is illustrated in listing 7-26. As you can see in listing 7-26, there are two routes between a Django relationship.

What is the relationship between Django models?

One to many Django model relationship The first Django model in listing 7-22 is Menu and has the name field (e.g. Menu instances can be Breakfast , Lunch, Drinks ,etc). Next, in listing 7-22 is the Item Django model which has a menu field, that itself has the models.ForeignKey (Menu) definition.

What is a one to many relationship in Salesforce?

A one to many relationship implies that one model record can have many other model records associated with itself. For example, a Menu model record can have many Item model records associated with it and yet an Item belongs to a single Menu record.


Video Answer


1 Answers

Funny problem ! I think I have the answer (get the team that beats yourteams the most):

Team.objects.get( # the expected result is a team
    pk=list( # filter out yourteams
        filter(lambda x: x not in [ y.id for y in yourteams ],
            list( 
                Match.objects # search matches
                .filter(matchteams__in=yourteams) # in which you were involved
                .filter(matchteams__winner=False) # that you loose
                .annotate(cnt=Count('teams')) # and count them
                .order_by('-cnt') # sort appropriately
                .values_list('teams__id', flat=True) # finally get only pks
            )
        )
    )[0] # take the first item that should be the super winner
)

I did not test it explicitly, but if does not work, I think it may be the right track.

like image 87
albar Avatar answered Sep 28 '22 01:09

albar