¿La forma más rápida de obtener el primer objeto de un conjunto de consultas en Django?

7 minutos de lectura

A menudo me encuentro queriendo obtener el primer objeto de un conjunto de consultas en Django, o regresar None si no hay ninguno. Hay muchas maneras de hacer esto y todas funcionan. Pero me pregunto cuál es el más eficaz.

qs = MyModel.objects.filter(blah = blah)
if qs.count() > 0:
    return qs[0]
else:
    return None

¿Resulta esto en dos llamadas a la base de datos? Eso parece un desperdicio. ¿Esto es más rápido?

qs = MyModel.objects.filter(blah = blah)
if len(qs) > 0:
    return qs[0]
else:
    return None

Otra opción sería:

qs = MyModel.objects.filter(blah = blah)
try:
    return qs[0]
except IndexError:
    return None

Esto genera una sola llamada a la base de datos, lo cual es bueno. Pero requiere crear un objeto de excepción la mayor parte del tiempo, lo cual requiere mucha memoria cuando todo lo que realmente necesita es una prueba if trivial.

¿Cómo puedo hacer esto con una sola llamada a la base de datos y sin agitar la memoria con objetos de excepción?

  • Regla general: si le preocupa minimizar los viajes de ida y vuelta de la base de datos, no utilice len() en los conjuntos de consultas, utilice siempre .count().

    – Daniel Di Paolo

    25 de febrero de 2011 a las 23:41

  • “crear un objeto de excepción la mayor parte del tiempo, lo cual requiere mucha memoria”: si le preocupa crear una excepción adicional, entonces lo está haciendo mal, ya que Python usa excepciones en todas partes. ¿Realmente comparó que es un uso intensivo de la memoria en su caso?

    – lqc

    3 de julio de 2012 a las 5:30

  • @Leopd Y si realmente hubieras evaluado la respuesta de alguna manera (o al menos los comentarios), sabrías que no es más rápido. En realidad, puede ser más lento, porque está creando una lista adicional solo para desecharla. ¡Y todo eso es solo una pizca en comparación con el costo de llamar a una función de python o usar el ORM de Django en primer lugar! Una sola llamada a filter() es muchas, muchas, muchos veces más lento que generar una excepción (que aún se generará, ¡porque así es como funciona el protocolo iterador!).

    – lqc

    5 de julio de 2012 a las 22:02


  • Su intuición es correcta de que la diferencia de rendimiento es pequeña, pero su conclusión es incorrecta. Ejecuté un punto de referencia y la respuesta aceptada es, de hecho, más rápida por un margen real. Imagínate.

    – leopardo

    6 de julio de 2012 a las 6:11

  • Para la gente que usa Django 1.6, finalmente agregaron el first() y last() métodos de conveniencia: docs.djangoproject.com/en/dev/ref/models/querysets/#first

    – Wei Yen

    12 de marzo de 2014 a las 3:55


avatar de usuario
cod3monk3y

Django 1.6 (lanzado en noviembre de 2013) introdujo el métodos de conveniencia first() y last() que tragan la excepción resultante y regresan None si el conjunto de consultas no devuelve objetos.

  • no hace el [:1]por lo que no es tan rápido (a menos que necesite evaluar todo el conjunto de consultas de todos modos).

    – janek37

    26 de febrero de 2016 a las 21:16

  • además, first() y last() hacer cumplir un ORDER BY cláusula en una consulta. Hará que los resultados sean deterministas, pero lo más probable es que ralentice la consulta.

    – Phil Krilov

    27 de junio de 2017 a las 19:24

  • @ janek37 no hay diferencias en el rendimiento. Como lo indica cod3monk3y, es un método conveniente y no lee todo el conjunto de consultas.

    – Zompa

    11 de septiembre de 2019 a las 9:55


  • @Zompa es incorrecto. HAY UNA DIFERENCIA EN EL RENDIMIENTO debido a la aplicación ORDER BY @Phil Krylov señaló, que [:1] evita

    – el locutor

    10 de marzo de 2021 a las 22:27


  • Se revirtió la edición, que no agregó ningún valor excepto la reformulación y sacó la sugerencia original de contexto. No estoy diciendo que first() y last() sean los lo más rápido manera, para el rendimiento, simplemente que estos métodos existen, son útiles y convenientes. No se afirma que esto responda al objetivo de rendimiento del OP. Pero claramente yo y otros hemos encontrado esta información marginalmente útil.

    – cod3monk3y

    25 de enero a las 20:36


avatar de usuario
levantatormentas

Puedes usar corte de matriz:

Entry.objects.all()[:1].get()

que se puede usar con .filter():

Entry.objects.filter()[:1].get()

No querrá convertirlo primero en una lista porque eso forzaría una llamada completa a la base de datos de todos los registros. Solo haz lo anterior y solo sacará el primero. Incluso podrías usar .order_by() para asegurarse de obtener el primero que desea.

Asegúrese de agregar el .get() o de lo contrario obtendrá un conjunto de consultas espalda y no un objeto.

  • Aún necesitaría envolverlo en un intento… excepto ObjectDoesNotExist, que es como la tercera opción original pero con corte.

    – Danny W. Adair

    9 de marzo de 2012 a las 2:55

  • ¿Cuál es el punto de establecer un LÍMITE si vas a llamar a get() al final? Deje que el ORM y el compilador SQL decidan qué es lo mejor para su backend (por ejemplo, en Oracle, Django emula LIMIT, por lo que dañará en lugar de ayudar).

    – lqc

    3 de julio de 2012 a las 7:55

  • Usé esta respuesta sin el final .get(). Si se devuelve una lista, devuelvo el primer elemento de la lista.

    –Keith John Hutchison

    6 de junio de 2013 a las 1:10

  • ¿Cuál es la diferencia de tener Entry.objects.all()[0] ??

    –James Lin

    16 de junio de 2013 a las 19:59

  • @JamesLin La diferencia es que [:1].get() plantea DoesNotExist, mientras que [0] genera IndexError.

    – Rópez

    6 de septiembre de 2013 a las 5:42


r = list(qs[:1])
if r:
  return r[0]
return None

  • Si activa el seguimiento, estoy bastante seguro de que incluso verá este complemento LIMIT 1 a la consulta, y no sé si puedes hacer algo mejor que esto. Sin embargo, internamente __nonzero__ en QuerySet se implementa como try: iter(self).next() except StopIteration: return false... por lo que no escapa a la excepción.

    – Ben Jackson

    26 de febrero de 2011 a las 0:00

  • @ben: QuerySet.__nonzero__() nunca se llama desde el QuerySet se convierte en un list antes de comprobar la veracidad. Sin embargo, aún pueden ocurrir otras excepciones.

    – Ignacio Vázquez-Abrams

    26 de febrero de 2011 a las 0:07

  • @Aron: Eso puede generar un StopIteration excepción.

    – Ignacio Vázquez-Abrams

    19 de abril de 2012 a las 1:21

  • convirtiendo a lista === llamada __iter__ para obtener un nuevo objeto iterador y llamarlo next método hasta StopIteration es aventado. Así que definitivamente habrá una excepción en alguna parte;)

    – lqc

    3 de julio de 2012 a las 5:35


  • Esta respuesta ahora está desactualizada, eche un vistazo a la respuesta de @cod3monk3y para Django 1.6+

    – ValAyal

    26 de septiembre de 2014 a las 14:48

Esto podría funcionar también:

def get_first_element(MyModel):
    my_query = MyModel.objects.all()
    return my_query[:1]

si está vacío, devuelve una lista vacía; de lo contrario, devuelve el primer elemento dentro de una lista.

  • Esto provoca un LÍMITE 1 en el SQL y he visto afirmaciones de que puede hacer que la consulta sea más lenta, aunque me gustaría ver que se corrobore: si la consulta solo devuelve un elemento, ¿por qué el LÍMITE 1 realmente debería afectar el rendimiento? Así que creo que la respuesta anterior está bien, pero me encantaría ver evidencia que lo confirme.

    – rauenza

    29 de junio de 2018 a las 16:17


  • Yo no diría “mejor”. Realmente depende de tus expectativas.

    – trigras

    9 de abril de 2020 a las 15:12

avatar de usuario
Nikolay Fominyh

Si planea obtener el primer elemento con frecuencia, puede extender QuerySet en esta dirección:

class FirstQuerySet(models.query.QuerySet):
    def first(self):
        return self[0]


class ManagerWithFirstQuery(models.Manager):
    def get_query_set(self):
        return FirstQuerySet(self.model)

Definir modelo como este:

class MyModel(models.Model):
    objects = ManagerWithFirstQuery()

Y utilízalo así:

 first_object = MyModel.objects.filter(x=100).first()

  • Call objects = ManagerWithFirstQuery as objects = ManagerWithFirstQuery() – NO OLVIDES LOS PARÉNTESIS – de todos modos, me ayudaste así que +1

    – Kamil

    5 sep 2013 a las 23:19


avatar de usuario
Naftali

puede ser asi

obj = model.objects.filter(id=emp_id)[0]

o

obj = model.objects.latest('id')

  • Call objects = ManagerWithFirstQuery as objects = ManagerWithFirstQuery() – NO OLVIDES LOS PARÉNTESIS – de todos modos, me ayudaste así que +1

    – Kamil

    5 sep 2013 a las 23:19


avatar de usuario
Ari

Deberías usar métodos django, como existe. Está ahí para que lo uses.

if qs.exists():
    return qs[0]
return None

  • Excepto, si lo entiendo correctamente, Python idiomático generalmente usa un Más fácil pedir perdón que permiso (EAFP) enfoque en lugar de un Mira antes de saltar Acercarse.

    – Humo grande

    12 de enero de 2016 a las 12:48

  • EAFP no es solo una recomendación de estilo, tiene razones (por ejemplo, verificar antes de abrir un archivo no evita errores). Aquí creo que la consideración relevante es que existe + obtener elemento causa dos consultas de base de datos, lo que puede ser indeseable según el proyecto y la vista.

    – Merwok

    18/09/2018 a las 20:29

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad