Django bir queryset ilk nesne almak için en hızlı yolu?


193

Genellikle kendimi ilk nesneyi Django'daki bir queryset'ten almak isteyen ya da yoksa geri dönmek isterim None. Tüm bunların işe yaradığı bunu yapmanın birçok yolu vardır. Ama hangisinin en iyi performans olduğunu merak ediyorum.

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

Bu iki veritabanı çağrısı ile sonuçlanıyor mu? Bu savurgan görünüyor. Bu daha hızlı mı?

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

Başka bir seçenek:

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

Bu iyi bir tek veritabanı çağrısı üretir. Ancak çoğu zaman bir istisna nesnesi oluşturmayı gerektirir, bu da gerçekten ihtiyacınız olan tek şey önemsiz bir if-test olduğunda yapılması çok bellek yoğun bir şeydir.

Bunu tek bir veritabanı çağrısı ile ve istisna nesnelerle bellek çalkalamadan nasıl yapabilirim?


21
Temel kural: DB gidiş- len()dönüşlerini en aza indirmekten endişe ediyorsanız , sorgu kümelerinde kullanmayın, her zaman kullanın .count().
Daniel DiPaolo

7
"çoğu zaman bir istisna nesnesi oluşturmak, bu çok fazla bellek yoğun bir şeydir" - eğer ekstra bir istisna oluşturmakla ilgileniyorsanız, Python her yerde istisnalar kullandığından bunu yanlış yapıyorsunuz. Durumunuzda bellek yoğunluğu konusunda gerçekten kıyaslama yaptınız mı?
lqc

1
@Leopd Ve aslında herhangi bir şekilde (veya en azından yorumlarda) anwser'ı karşılaştırırsanız, bunun daha hızlı olmadığını bilirsiniz. Aslında daha yavaş olabilir, çünkü sadece dışarı atmak için ekstra bir liste oluşturursunuz. Ve her şey bir python fonksiyonunu arama veya Django'nun ORM'sini kullanma maliyetine kıyasla sadece fıstık! Tek bir filter () çağrısı çok, çok, birçok kez daha yavaştır ve bir istisna oluşturur (bu hala yükseltilecektir, çünkü yineleyici protokolü böyle çalışır!).
lqc

1
Sezginiz, performans farkının küçük olduğu doğrudur, ancak sonucunuz yanlıştır. Bir kıyaslama yaptım ve kabul edilen cevap aslında gerçek bir farkla daha hızlı. Git şekil.
Leopd

11
Django 1.6 kullanan kişiler için nihayet first()ve last()kolaylık yöntemlerini eklediler
Wei Yen

Yanıtlar:


328

(2013 Kasım yayınlandı) Django 1.6 tanıtıldı kolaylık yöntemleri first() ve last()hangi çıkan durum ve getiri yutmak NoneQuerySet hiçbir nesne dönerse.


2
[: 1] yapmaz, bu yüzden o kadar hızlı değildir (tüm sorgu kümesini yine de değerlendirmeniz gerekmedikçe).
janek37

13
Ayrıca, first()ve last()bir uygulamak ORDER BYbir sorguyu maddesini. Sonuçları deterministik hale getirecek, ancak büyük olasılıkla sorguyu yavaşlatacaktır.
Phil Krylov

@ janek37 Performansta hiçbir fark yok. Cod3monk3y ile belirtildiği gibi, uygun bir yöntemdir ve tüm queryset'i okumaz.
Zompa

143

Doğru cevap

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

Hangi kullanılabilir:

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

İlk olarak listeye dönüştürmek istemezsiniz çünkü bu, tüm kayıtların tam veritabanı çağrısını zorlar. Sadece yukarıdakileri yapın ve sadece ilkini çekecektir. İstediğiniz .order_byilkini almanızı sağlamak için bile kullanabilirsiniz .

Eklediğinizden emin olun, .get()yoksa bir nesne değil, bir Sorgu Kümesi geri alırsınız.


9
Orijinal üçüncü seçenek gibi ama dilimleme ile ObjectDoesNotExist hariç, yine de bir denemede sarmanız gerekir.
Danny W. Adair

1
Sonunda get () diyecekseniz LIMIT belirlemenin anlamı nedir? ORM ve SQL derleyicisinin arka uç için neyin en iyi olduğuna karar vermesine izin verin (örneğin, Oracle Django'da LIMIT öykünür, bu yüzden yardım etmek yerine incinir).
lqc

Bu cevabı sondaki .get () olmadan kullandım. Bir liste döndürülürse, listenin ilk öğesini döndürürüm.
Keith John Hutchison

sahip olmanın farkı nedir Entry.objects.all()[0]??
James Lin

15
@JamesLin Fark, [: 1] .get () işlevinin DoesNotExist'i, [0] ise IndexError değerini yükseltmesidir.
Ropez

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

1
Eğer izlemeyi açarsanız LIMIT 1, sorgunun bu eklemesini bile göreceğinize eminim ve bundan daha iyisini yapabileceğinizi bilmiyorum. Ancak, dahili __nonzero__içinde QuerySetolarak uygulanmaktadır try: iter(self).next() except StopIteration: return false...o istisna kaçamaz bu yüzden.
Ben Jackson

@ Ben: QuerySet.__nonzero__()asla doğruluk kontrol edilmeden önce QuerySetbir dönüştürülür çünkü asla çağrılır list. Ancak yine de başka istisnalar olabilir.
Ignacio Vazquez-Abrams

@Aron: Bu bir StopIterationistisna oluşturabilir .
Ignacio Vazquez-Abrams

__iter__yeni bir yineleyici nesnesi almak ve nextatılana kadar yöntemini çağırmak için list === çağrısına dönüştürmek StopIteration. Yani kesinlikle bir yerde bir istisna olacak;)
lqc

14
Bu cevap şimdi güncel değil, Django 1.6+ için @ cod3monk3y cevabına bir göz atın
ValAyal

37

Şimdi, Django 1.9'da first() querysets için yöntem var .

YourModel.objects.all().first()

Bu, queryset boşsa istisna atmadığından .get()veya daha iyi bir yoldur [0], Therafore, kullanarak kontrol etmenize gerek yokturexists()


1
Bu SQL'de bir LIMIT 1 neden olur ve ben bu doğrulamayı görmek istiyorum rağmen sorguyu yavaşlatabilir iddiaları gördüm: Sorgu sadece bir öğe döndürürse, LIMIT 1 neden performansı gerçekten etkilemek gerekir? Bu yüzden yukarıdaki cevabın iyi olduğunu düşünüyorum, ancak kanıtların doğrulandığını görmek isterim.
rrauenza

"Daha iyi" demezdim. Gerçekten beklentilerinize bağlı.
trigras

7

İlk öğeyi sık sık almayı planlıyorsanız - QuerySet'i bu yönde genişletebilirsiniz:

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


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

Modeli şu şekilde tanımlayın:

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

Ve şu şekilde kullanın:

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

Nesneleri çağırın = ManagerWithFirstQuery nesneler olarak = ManagerWithFirstQuery () - BAĞIŞ UNUTMAYIN - her neyse, bana yardım ettin +1
Kamil

7

Bu da işe yarayabilir:

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

boşsa, boş bir liste döndürür, aksi takdirde listenin içindeki ilk öğeyi döndürür.


1
Bu, bugüne kadarki en iyi çözümdür ... Veritabanına yalnızca bir çağrı ile sonuçlanır
Şşş

5

Böyle olabilir

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

veya

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

3

Var olduğu gibi django yöntemlerini kullanmalısınız. Kullanmanız için orada.

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

1
Eğer doğru anlıyorsam, idiyomatik Python genellikle Sıçramadan Önce Bak yaklaşımından ziyade Bağışlanmak için İzin Daha Kolay ( EAFP ) yaklaşımını kullanır.
BigSmoke

EAFP sadece bir stil önerisi değildir, bunun nedenleri de vardır (örneğin, bir dosyayı açmadan önce kontrol etmek hataları önlemez). Burada ilgili düşünce var var + öğe ve proje ve görünüme bağlı olarak istenmeyen iki veritabanı sorguları neden olsun.
Éric Araujo

2

Django 1.6'dan beri first () yöntemiyle filter () yöntemini şu şekilde kullanabilirsiniz:

Model.objects.filter(field_name=some_param).first()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.