2012-03-23 11 views
12

Tengo una tabla de base de datos llamada 'estudiante' en la que hay una columna llamada 'marcas'. Quiero el registro del estudiante con las calificaciones más altas en Matemáticas. Hay una solución simple a él utilizando order_by()[0]:Django: registro con el elemento máximo

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

Pero esto ordena la tabla y luego me va a buscar el primer registro. Si mi tabla es enorme, esto es redundante ya que solo necesito el registro máximo. ¿Hay alguna forma de obtener el mayor valor sin ordenar?

Quiero el objeto completo, no solo el valor máximo.

Gracias Anuj

Respuesta

22

El SQL requerido sería algo como esto:

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

hacerlo a través de Django, puede utilizar la aggregation API.

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

Lamentablemente, esta consulta es en realidad dos consultas. Se ejecuta la agregación de marca máxima, el resultado se extrae en python y luego se pasa a la segunda consulta. No hay forma (sorprendentemente) de pasar un conjunto de consulta que sea simplemente una agregación sin una agrupación, aunque debería ser posible hacerlo. Voy a abrir un ticket para ver cómo se puede solucionar.

Editar:

Se es posible hacer esto con una sola consulta, pero no es muy obvio. No he visto este método en otro lado.

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) 

Si imprime esta consulta en la cáscara que se obtiene:

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)) 

probado en Django 1.11 (actualmente en alfa). Esto funciona agrupando la anotación por la constante 1, en la que se agruparán todas las filas. Luego eliminamos esta columna de agrupación de la lista de selección (la segunda values(). Django (ahora) sabe lo suficiente como para determinar que la agrupación es redundante y la elimina. Dejando una sola consulta con el SQL exacto que necesitamos.

+0

¿Cuál es el F en F ('max_mark')? –

+0

@ChadVernon, se llaman expresiones F(). Le permiten usar el valor de otra columna. https://docs.djangoproject.com/en/dev/topics/db/queries/#query-expressions –

+2

Esto no funciona. El SQL resultante es algo así como: 'SELECT *, MAX (" mark ") AS" max_mark "FROM STUDENT QUE TIENE" STUDENT "." Mark "= (MAX (" STUDENT "." Marks "))', que simplemente selecciona todo estudiantes. –

2

Esta pregunta puede ser atento a que: How to do SELECT MAX in Django?

sólo tiene que utilizar la agregación.

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

No probado, pero debería funcionar. :)

0

Con un En teoría, no hay forma posible de que la base de datos recupere el valor máximo sin primero clasificarlo. Solo piense en ello, ¿cómo puede la base de datos saber cuál es el valor máximo a menos que se vea en cada fila?

Por supuesto, eso es con una configuración muy ingenua. Afortunadamente tiene dos opciones disponibles:

  1. usa un índice. Si crea un índice en esa columna, la clasificación generalmente puede aprovechar el índice, ahorrándole un escaneo completo de la tabla.

  2. normalize (aka precompute). Cree otra tabla en alguna parte que almacene el valor máximo, y asegúrese de verificarla/actualizarla cada vez que se agregue/modifique/elimine un objeto Estudiante.

Sin conocer más de los requisitos, recomiendo usar el índice.

Salida: https://docs.djangoproject.com/en/dev/ref/models/fields/#db-index

+0

Esta pregunta parece ser más sobre el ORM de Django y no sobre las estructuras de la base de datos o de las tablas, a lo que se orienta su respuesta. – TheCatParty

+1

Teóricamente, encontrar el máximo es una operación o (n); solo tiene que visitar cada elemento una vez, que es diferente de la ordenación. Para ordenar necesita hacer comparaciones o (nlogn). – abhaga

+0

@abhaga El objetivo principal de las bases de datos, en mi opinión, es permitir el acceso rápido a los datos. Al crear un índice, la base de datos se mantiene ordenada de modo que acceder al máximo (o mínimo, o compensación) es una operación O (1). –

Cuestiones relacionadas