145

I have a list of objects how can I run a query to give the max value of a field:

I'm using this code:

def get_best_argument(self):
    try:
        arg = self.argument_set.order_by('-rating')[0].details
    except IndexError:
        return 'no posts'
    return arg

rating is an integer

avenet
  • 2,894
  • 1
  • 19
  • 26
Johnd
  • 6,441
  • 9
  • 29
  • 22

8 Answers8

247

See this. Your code would be something like the following:

from django.db.models import Max
# Generates a "SELECT MAX..." query
Argument.objects.aggregate(Max('rating')) # {'rating__max': 5}

You can also use this on existing querysets:

from django.db.models import Max
args = Argument.objects.filter(name='foo') # or whatever arbitrary queryset
args.aggregate(Max('rating')) # {'rating__max': 5}

If you need the model instance that contains this max value, then the code you posted is probably the best way to do it:

arg = args.order_by('-rating')[0]

Note that this will error if the queryset is empty, i.e. if no arguments match the query (because the [0] part will raise an IndexError). If you want to avoid that behavior and instead simply return None in that case, use .first():

arg = args.order_by('-rating').first() # may return None
Sasha Chedygov
  • 127,549
  • 26
  • 102
  • 115
  • 5
    I need the actuall argument object that has that Max, so I can print the details field. The args.aggregate(Max('rating')) call just returns the highest rating. I'm looking for the arg with the highest rating. – Johnd May 11 '09 at 07:32
  • 1
    What's wrong with your exiting code - Argument.objects.order_by("-rating")[0]? – AdamKG May 11 '09 at 11:44
  • Ordering is not efficient. – keramat Sep 25 '22 at 10:21
  • @keramat: In what way? Ordering is very efficient if you have an index on the field you're ordering by. – Sasha Chedygov Oct 05 '22 at 22:40
  • Actually, in large databases with millions of records ordering is very time consuming. – keramat Oct 06 '22 at 06:45
  • 1
    @keramat: It's not useful to say something is "not efficient" or "very time consuming" without any context. If you have an index on the field you are ordering by that's sorted in the same direction, as well as all other fields you are filtering on, then the above query will be fast regardless of the size of the DB. It's best to have a multi-column index in this case, but it can be fast even with multiple indexes. See [the docs](https://www.postgresql.org/docs/current/indexes-ordering.html) on the topic. (Source: I've done optimization work on a DB with hundreds of millions of records.) – Sasha Chedygov Oct 06 '22 at 21:00
88

Django also has the 'latest(field_name = None)' function that finds the latest (max. value) entry. It not only works with date fields but also with strings and integers.

You can give the field name when calling that function:

max_rated_entry = YourModel.objects.latest('rating')
return max_rated_entry.details

Or you can already give that field name in your models meta data:

from django.db import models

class YourModel(models.Model):
    #your class definition
    class Meta:
        get_latest_by = 'rating'

Now you can call 'latest()' without any parameters:

max_rated_entry = YourModel.objects.latest()
return max_rated_entry.details
Lutz Schönemann
  • 1,069
  • 7
  • 7
  • 3
    This is a great way of using `latest()`. If you need the record with the minimum value, you can use `earliest()`. – SaeX May 21 '16 at 09:18
  • 8
    `latest()` and `earliest()` works with non-date field too, but it is a side-effect of the implementation. You should use `.order_by('').first()` or `.order_by('').last()` to ensure your code will still works even if Django developers will change `latest()` and `earliest()` implementation to work with date fields only. – mrnfrancesco Jul 01 '17 at 14:45
  • 2
    This is a bad answer: 1) as already noticed earlier it's detail of implementation and can be changed in the future 2) it's not very readable - max number is not necessarily latest saved one (or in any other sense) – Mikhail Gerasimov Jun 19 '19 at 09:27
59

I've tested this for my project, it finds the max/min in O(n) time:

from django.db.models import Max

# Find the maximum value of the rating and then get the record with that rating. 
# Notice the double underscores in rating__max
max_rating = App.objects.aggregate(Max('rating'))['rating__max']
return App.objects.get(rating=max_rating)

This is guaranteed to get you one of the maximum elements efficiently, rather than sorting the whole table and getting the top (around O(n*logn)).

funnydman
  • 9,083
  • 4
  • 40
  • 55
afahim
  • 619
  • 5
  • 7
  • 8
    Big-O sometimes doesn't bear out in practice, especially for smaller n, where coefficients and implementation matter. Your code is in python/django, which is compiled to bytecode and may or may not be optimized. The database is likely written in a language that is optimized and compiled to machine instructions. Besides, the database has no real reason to sort, if the function is only looking for a max value. I'd like to see timing data before I'm comfortable using hand-build code over a built-in database function. – benevolentprof Aug 18 '13 at 13:36
  • 10
    @afahim I don't think this the code you post is completely correct. if turns out you have more than one maximum (let's say you have two Apps with 5 stars) using "get" will raise an error. – Raydel Miranda Jul 06 '18 at 19:05
  • Another issue with this approach is that in general there is no guarantee that after computing the maximum the record with it has not been deleted before querying it. To ensure that, you would need to execute these two database statements in a transactions while locking the whole table using `select_for_update`, which has a major performance impact on the database. – roskakori Jan 05 '22 at 07:56
11

sol 01:

from .models import MyMODEL

max_rating = MyMODEL.objects.order_by('-rating').first()

sol 02:

from django.db.models import Max
from .models import MyMODEL

max_rating = MyMODEL.objects.aggregate(Max('rating'))
Dipta Dhar
  • 121
  • 1
  • 3
8

If you also want to get a value other than None in case the table is empty (e.g. 0), combine Max with Coalesce:

from django.db.models import Max, Value
from django.db.models.functions import Coalesce

max_rating = SomeModel.objects.aggregate(
    max_rating=Coalesce(Max('rating'), Value(0))
)['max_rating']
roskakori
  • 3,139
  • 1
  • 30
  • 29
3

To maybe improve on @afahim answer with regards to @Raydel Miranda comment, if you want a random comment. If you want all, then use just the filter

from django.db.models import Max

# Find the maximum value of the rating and then get the record with that rating. 
# Notice the double underscores in rating__max
max_rating = App.objects.aggregate(Max('rating'))['rating__max']
return App.objects.filter(rating=max_rating).first()
1

maybe it will help someone's trouble, in CBV

def get_queryset(self):
    sorgu = Sunum.objects.values('id', 'firma', 'projeadi', 'sunumdurum__durum', 'sunumdurum__aciklama'
                           ).annotate(max_rank=Max('sunumdurum__kayittarihi'))
    szlk={}
    for sor in sorgu :
        ana = sor['id'], sor['firma'], sor['projeadi']
        dana = sor['sunumdurum__durum'], sor['sunumdurum__aciklama'], sor['max_rank']
        szlk.setdefault(ana, dana)
    return szlk
ZeQ
  • 51
  • 3
  • 9
1

For future readers you can use annotate instead of aggregate if you need the record / other fields and not just the max value.

i.e equivalent to this sql query

SELECT
  first_name,
  last_name,
  Max(height) max_height
FROM patients;

Now one problem is passing an aggregate function in annotate will trigger a group by

from django.db.models import Max

Patients.objects.values('height').annotate(max_height=Max('height'))

>>>
SELECT "patients"."height",
       MAX("patients"."height") AS "max_height"
FROM "patients"
GROUP BY "patients"."height"

The workaround is to use the Func expression

from django.db.models F, Func

Patients.objects.annotate(max_height=Func(F('height'), function='Max'))\
.values('first_name', 'last_name', 'max_height')

>>>
SELECT "patients"."first_name",
       "patients"."last_name",
       Max("patients"."height") AS "max_height"
FROM "patients"

see this if you need to do this in a subquery

Julkar9
  • 1,508
  • 1
  • 12
  • 24