Good ways to sort a queryset? - Django

PythonDjangoDjango Models

Python Problem Overview


what I'm trying to do is this:

  • get the 30 Authors with highest score ( Author.objects.order_by('-score')[:30] )

  • order the authors by last_name


Any suggestions?

Python Solutions


Solution 1 - Python

What about

import operator

auths = Author.objects.order_by('-score')[:30]
ordered = sorted(auths, key=operator.attrgetter('last_name'))

In Django 1.4 and newer you can order by providing multiple fields.
Reference: https://docs.djangoproject.com/en/dev/ref/models/querysets/#order-by

*order_by(fields)

By default, results returned by a QuerySet are ordered by the ordering tuple given by the ordering option in the model’s Meta. You can override this on a per-QuerySet basis by using the order_by method.

Example:

ordered_authors = Author.objects.order_by('-score', 'last_name')[:30]

The result above will be ordered by score descending, then by last_name ascending. The negative sign in front of "-score" indicates descending order. Ascending order is implied.

Solution 2 - Python

I just wanted to illustrate that the built-in solutions (SQL-only) are not always the best ones. At first I thought that because Django's QuerySet.objects.order_by method accepts multiple arguments, you could easily chain them:

ordered_authors = Author.objects.order_by('-score', 'last_name')[:30]

But, it does not work as you would expect. Case in point, first is a list of presidents sorted by score (selecting top 5 for easier reading):

>>> auths = Author.objects.order_by('-score')[:5]
>>> for x in auths: print x
... 
James Monroe (487)
Ulysses Simpson (474)
Harry Truman (471)
Benjamin Harrison (467)
Gerald Rudolph (464)

Using Alex Martelli's solution which accurately provides the top 5 people sorted by last_name:

>>> for x in sorted(auths, key=operator.attrgetter('last_name')): print x
... 
Benjamin Harrison (467)
James Monroe (487)
Gerald Rudolph (464)
Ulysses Simpson (474)
Harry Truman (471)

And now the combined order_by call:

>>> myauths = Author.objects.order_by('-score', 'last_name')[:5]
>>> for x in myauths: print x
... 
James Monroe (487)
Ulysses Simpson (474)
Harry Truman (471)
Benjamin Harrison (467)
Gerald Rudolph (464)

As you can see it is the same result as the first one, meaning it doesn't work as you would expect.

Solution 3 - Python

Here's a way that allows for ties for the cut-off score.

author_count = Author.objects.count()
cut_off_score = Author.objects.order_by('-score').values_list('score')[min(30, author_count)]
top_authors = Author.objects.filter(score__gte=cut_off_score).order_by('last_name')

You may get more than 30 authors in top_authors this way and the min(30,author_count) is there incase you have fewer than 30 authors.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRadiantHexView Question on Stackoverflow
Solution 1 - PythonAlex MartelliView Answer on Stackoverflow
Solution 2 - PythonjathanismView Answer on Stackoverflow
Solution 3 - PythonistrubleView Answer on Stackoverflow