2011-02-25 21 views
131

A menudo me parece que quiero obtener el primer objeto de un queryset en Django, o devolver None si no hay ninguno. Hay muchas formas de hacer esto que funcionan. Pero me pregunto cuál es el más eficiente.¿La forma más rápida de obtener el primer objeto de un conjunto de consulta en django?

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

¿Esto da como resultado 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 base de datos, lo cual es bueno. Pero requiere crear un objeto de excepción la mayor parte del tiempo, lo cual es una tarea muy intensiva en memoria cuando todo lo que realmente necesita es una prueba 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?

+12

Regla de oro: si le preocupa minimizar los viajes de ida y vuelta de DB, no use 'len()' en los conjuntos de consulta, siempre use '.count()'. –

+5

"crear un objeto de excepción muchas veces, que es una tarea muy intensiva en memoria": si le preocupa crear una excepción adicional, lo está haciendo mal ya que Python usa excepciones en todo el lugar. ¿Hizo una comparación real de que en su caso consume mucha memoria? – lqc

+1

@Leopd Y si hubieras comparado el anwser 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 crea una lista adicional solo para desecharla. ¡Y todo eso es solo un cacahuete comparado con el costo de llamar a una función de pitón o usar el ORM de Django en primer lugar! Una sola llamada a filter() es muchas, muchas, * muchas * veces más lenta que generar una excepción (que aún se va a plantear, porque así es como funciona el protocolo de iterador). – lqc

Respuesta

247

Django 1.6 (released Nov 2013) introdujo el convenience methodsfirst() y last() que tragar la excepción resultante y volver None si el queryset no devolvió objetos.

+1

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

+4

también, 'first()' y 'last()' imponen una cláusula 'ORDER BY' en una consulta. Hará que los resultados sean deterministas, pero probablemente ralentizará la consulta. –

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

Si activa el rastreo, estoy bastante seguro de que incluso verá este agrega '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() excepto StopIteration: return false ...' por lo que no escapa a la excepción. –

+0

@Ben: 'QuerySet .__ nonzero __()' nunca se llama porque 'QuerySet' se convierte en una' lista' antes de verificar la veracidad. Sin embargo, otras excepciones todavía pueden ocurrir. –

+0

@Aron: eso puede generar una excepción 'StopIteration'. –

122

La respuesta correcta es

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

que se puede utilizar en:

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

que no le gustaría a su vez primera en una lista, ya que obligaría a una Llamada de base de datos completa de todos los registros. Simplemente haz lo anterior y solo extraerá el primero. Incluso podría usar .order_by para asegurarse de obtener el primero que desea.

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

+8

. Aún así debería envolverlo en un intento ... excepto ObjectDoesNotExist, que es como la tercera opción original pero con cortar. –

+1

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

+0

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

7

Si va a obtener el primer elemento a menudo - se 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 así:

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

y utilizarlo como esto:

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

Llamar objetos = ManagerWithFirstQuery como objects = ManagerWithFirstQuery() - NO OLVIDAR PARÉNTESIS - de todos modos, me ayudaste así que +1 – Kamil

3

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

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

Excepto que, si entiendo tan bien, Python idiomático normalmente usa un _Easier para pedir perdón que permiso_ ([EAFP] (https://docs.python.org/2/glossary.html#term-eafp)) enfoque en lugar de un enfoque _Busque antes de saltar_. – BigSmoke

4

Puede ser como este

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

o

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

Ahora, en Django 1.9 tienes first() método para querysets.

YourModel.objects.all().first() 

Ésta es una manera mejor que .get() o [0], ya que no produce una excepción si queryset está vacía, Therafore, no es necesario comprobar mediante exists()

2

esto podría funcionar así:

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

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

Cuestiones relacionadas