简体   繁体   中英

Filter queryset to return only the best result for each user

I have a couple of django models, one of which holds a number of user results for different events. I'm looking for a way to generate a queryset consisting of only the best (highest) result for each user that also has the other attributes attached (like the date of the result).

My models are as shown as well as using the built in user model:

class CombineEvents(models.Model):
    team = models.ForeignKey(Team)
    event = models.CharField(max_length=100)
    metric = models.CharField(max_length=100)
    lead_order = models.IntegerField()

    def __unicode__(self):
        return self.event

class CombineResults(models.Model):
    user = models.ForeignKey(User)
    date = models.DateField()
    event = models.ForeignKey(CombineEvents)
    result = models.FloatField()

    def __unicode__(self):
        return str(self.date) + " " + str(self.event)

I am iterating through each event and attaching a queryset of the events results, which is working fine, but I want that sub-queryset to only include one object for each user and that object should be that user's best result. My queryset code is below:

    combine_events = CombineEvents.objects.filter(team__id=team_id)
    for event in combine_events:
        event.results = CombineResults.objects.filter(event=event)

I'm not sure how filter down to just those best results for each user. I want to use these querysets to create leaderboards, so I'd still like to be able to also have the date of that best result and the user name, but don't want the leaderboard to allow more than one spot per user. Any ideas?

Since your CombineResults model has a FK relation to CombineEvents , you can do something like this:

combine_events = CombineEvents.objects.filter(team__id=team_id)
for event in combine_events:
    result = event.combineresults_set.order_by('-result')[0]

The combineresults_set attribute is auto-generated by the FK field, though you can set it to something more helpful by specifying the related_name keyword argument:

class CombineResults(models.Model):
    event = models.ForeignKey(CombineEvents, related_name='results')

would enable you to call event.results.order_by(...) . There is more in the documentation here: https://docs.djangoproject.com/en/1.9/topics/db/queries/#following-relationships-backward

Note that this isn't the most DB-friendly approach as you will effectively hit the database once to get combine_events (as soon you start iterating), and then again for each event in that list. It will probably be better to use prefetch_related() , which you can use to make two DB queries only. Documentation can be found here .

prefetch_related() however will default to do a queryset.all() for the related documents, which you could further control by using Prefetch objects as documented here .

Edit: Apologies for getting the question wrong. Getting every user's best result per event (which is what I think you want) is not quite as simple. I'd probably do something like this:

from django.db.models import Q, Max
combine_events = CombineEvents.objects \
    .filter(team_id=team_id) \
    .prefetch_related('combineresults_set')

for event in combine_events:
    # Get the value of the best result per user
    result = event.combineresults_set.values('user').annotate(best=Max('result'))

    # Now construct a Q() object, note this will evaluate the result query
    base_q = Q()
    for res in result:
        # this is the equivalent to base_q = base_q | ....
        base_q |= (Q(user_id=res['user']) & Q(result=res['best']))

    # Now you're ready to filter results
    result = event.combineresults_set.filter(base_q)

You can read more about Q objects here , or alternatively write your own SQL using RawSQL and the likes. Or wait for someone with a better idea..

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM