Джанго: запись с максимальным элементом

У меня есть таблица базы данных с именем 'student', в которой есть один столбец с именем 'marks'. Я хочу студенческий рекорд с самыми высокими оценками по математике. Есть простое решение, используя order_by()[0]:

Student.objects.filter(subject='Maths').order_by('-marks')[0]

Но это сортирует таблицу, а затем извлекает мне первую запись. Если моя таблица огромна, это избыточно, так как мне нужна только максимальная запись. Есть ли способ просто получить наибольшее значение без сортировки?

Я хочу весь объект, а не только максимальное значение.

Спасибо анудж

5 ответов

Решение

Требуемый SQL будет примерно таким:

SELECT *
FROM STUDENT
WHERE marks = (SELECT MAX(marks) FROM STUDENT)

Чтобы сделать это через Django, вы можете использовать API агрегирования.

max_marks = Student.objects.filter(
    subject='Maths'
).aggregate(maxmarks=Max('marks'))['maxmarks']
Student.objects.filter(subject='Maths', marks=max_marks)

К сожалению, этот запрос на самом деле два запроса. Выполняется агрегация максимальной отметки, результат выводится в python, а затем передается во второй запрос. (Удивительно), но нет способа передать набор запросов, который является просто агрегацией без группировки, даже если это возможно. Я собираюсь открыть билет, чтобы посмотреть, как это можно исправить.

Редактировать:

Это можно сделать одним запросом, но это не очень очевидно. Я не видел этот метод в другом месте.

from django.db.models import Value

max_marks = (
    Student.objects
           .filter(subject='Maths')
           .annotate(common=Value(1))
           .values('common')
           .annotate(max_marks=Max('marks'))
           .values('max_marks')
)

Student.objects.filter(subject='Maths', marks=max_marks)

Если вы напечатаете этот запрос в оболочке, вы получите:

SELECT 
       "scratch_student"."id", 
       "scratch_student"."name", 
       "scratch_student"."subject", 
       "scratch_student"."marks" 
  FROM "scratch_student" 
 WHERE ( 
       "scratch_student"."subject" = Maths 
   AND "scratch_student"."marks" = (
       SELECT 
              MAX(U0."marks") AS "max_marks" 
         FROM "scratch_student" U0 
        WHERE U0."subject" = Maths))

Проверено на Django 1.11 (в настоящее время в альфа-версии). Это работает, группируя аннотацию по константе 1, в которую будет группироваться каждая строка. Затем мы удаляем этот столбец группировки из списка выбора (второй values(), Джанго (сейчас) знает достаточно, чтобы определить, что группировка избыточна, и устраняет ее. Оставив один запрос с нужным нам SQL.

Этот вопрос может быть вам полезен: как сделать SELECT MAX в Django?

Просто используйте агрегацию.

from django.db.models import Max
Student.objects.filter(subject='Math').aggregate(Max('marks'))

Не проверено, но должно работать.:)

Если вы имели в виду, что вам нужна одна запись с наивысшей оценкой по математике, я думаю, что использование SQL LIMIT более очевидно и предпочтительнее:

      Student.objects.filter(subject='Maths').order_by('-marks')[:1].get()

SQL будет таким:

      SELECT * FROM student WHERE subject = 'Maths' ORDER BY marks DESC LIMIT 1

Но имейте в виду, что может быть несколько учеников с наивысшей оценкой, и вы получите случайного.

С простой таблицей базы данных теоретически невозможно, чтобы база данных могла получить максимальное значение для вас без предварительной сортировки. Подумайте только, как база данных может узнать, какое максимальное значение, если она не просматривает каждую строку?

Конечно, это с очень наивной настройкой. К счастью, у вас есть два варианта:

  1. использовать индекс. Если вы создаете индекс для этого столбца, сортировка обычно может использовать индекс, сохраняя при этом полное сканирование таблицы.

  2. нормализовать (он же предварительный расчет). Создайте еще одну таблицу, в которой будет храниться максимальное значение, и проверяйте / обновляйте ее каждый раз, когда объект Student добавляется / изменяется / удаляется.

Не зная больше требований, я настоятельно рекомендую использовать индекс.

Проверьте: https://docs.djangoproject.com/en/dev/ref/models/fields/

      from django.db.models import Max

temp = Student.objects.filter(subject='Math').aggregate(Max('marks'))
record = Student.objects.filter(Q(subject='Math') & Q(subject=temp['marks__max']))
Другие вопросы по тегам