2011-02-25 16 views
131

しばしば、私はDjangoのクエリーセットから最初のオブジェクトを取得したい場合があります。ない場合はNoneを返します。これを行うには多くの方法がありますが、それはすべて機能します。しかし、私はどの演奏家が演奏しているのか不思議です。djangoのクエリーセットから最初のオブジェクトを取得する最速の方法は?

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

この結果、2回のデータベース呼び出しが行われますか?それは無駄に思える。これはもっと速いですか?

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

別のオプションは、次のようになります。

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

これは良いですが、単一のデータベース呼び出しを生成します。しかし、多くの場合、例外オブジェクトを作成する必要があります。これは、本当に必要なものが簡単なif-testである場合に、非常にメモリを消費することです。

例外オブジェクトを使用してメモリを混乱させることなく、単一のデータベース呼び出しでこれを行うにはどうすればよいですか?

+12

DBの往復を最小限に抑えることを心配している場合は、クエリセットで 'len()'を使わないでください。常に '.count()'を使用してください。 –

+5

"非常にメモリ集約的なものですが、多くの時間をかけて例外オブジェクトを作成します" - 例外が1つ発生することが懸念される場合は、Pythonが例外をすべて使用するため、間違っています。実際にあなたのケースではメモリを大量に消費することをベンチマークしましたか? – lqc

+1

@Leopdそして、もしあなたがアンウォースを実際にベンチマークしたのであれば、それはそれほど速くはないことが分かります。それは実際には遅くなるかもしれません。あなたがそれを投げ捨てるだけの余分なリストを作成するからです。 Python関数を呼び出すかDjangoのORMを使うのと比べると、ちょうどピーナッツです! filter()への1回の呼び出しは、多くの場合、多くの場合、*多く*遅くなり、例外が発生します(これはまだ起こりそうですが、それがイテレータプロトコルの仕組みです)。 – lqc

答えて

247

Django 1.6 (released Nov 2013)ようにすることができるconvenience methodsfirst()得られた例外を飲み込むと、クエリセットは何のオブジェクトが返されない場合Noneを返すlast()を導入しました。

+1

それは[:1]を実行しないので、高速ではありません(とにかくクエリーセット全体を評価する必要がない場合)。 – janek37

+4

また、 'first()'と 'last()'はクエリに対して 'ORDER BY'節を強制します。結果は決定的になりますが、おそらくクエリが遅くなります。 –

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

を参照してください。トレースをオンにすると、これも表示されますクエリに 'LIMIT 1 'を追加してください。これ以上のことはできません。しかし、 'QuerySet'の内部で' __nonzero__'は 'try:iter(self).next()以外のStopIteration:return false ...'として実装されているため、例外をエスケープしません。 –

+0

@Ben:真偽をチェックする前に 'QuerySet'が' list'に変換されているので、 'QuerySet .__ nonzero __()'は呼び出されません。ただし、その他の例外が引き続き発生することがあります。 –

+0

@Aron:それは 'StopIteration'例外を生成する可能性があります。 –

122

正解がで使用することができ

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

である:それは強制するので、あなたが最初のリストにそれを回すのは嫌だ

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

すべてのレコードの完全なデータベース呼び出し。ちょうど上記を実行し、それは最初のものを引っ張ります。 .order_byを使用して、あなたが望むものを確実に得ることができます。

必ず.get()を追加してください。そうしないと、オブジェクトではなくQuerySetが返されます。

+8

の答えを試してみてください。元の3番目のオプションのようにスライスしているObjectDoesNotExistを除いて、試しにラップする必要があります。 –

+1

最後にget()を呼び出す場合は、LIMITを設定するのは何ですか? ORMとSQLコンパイラが、バックエンドに最適なものを決定させます(たとえば、Oracle DjangoはLIMITをエミュレートするため、支援するのではなく怪我をするでしょう)。 – lqc

+0

私はこの答えを末尾の.get()なしで使用しました。リストが返された場合は、リストの最初の要素を返します。 –

7

あなたは、多くの場合、最初の要素を取得することを計画している場合 - あなたはこの方向にクエリセットを拡張することができます。

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

そして、このようにそれを使用します:

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

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


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

は、このようなモデルを定義します

+0

オブジェクトとしてManagerWithFirstQueryを呼びます= ManagerWithFirstQuery() - とにかく助けてくれました+1 – Kamil

3

存在するようなdjangoメソッドを使用する必要があります。あなたのためにそこにそれを使用します。

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

私が正しく理解している場合を除いて、慣用句Pythonは一般的に_Easierを使用してPermission_([EAFP] https://docs.python.org/2/glossary.html#term-eafp))アプローチではなく、_Look Before You Leap_アプローチを採用しています。 – BigSmoke

4

はこの

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

又は

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

今、Django 1.9には、クエリセット用のfirst()メソッドがあります。

YourModel.objects.all().first() 

クエリセットが空の場合、それは、Theraforeを例外をスローしませんので、これは、.get()または[0]よりも良い方法ですあなたはこれが同様に動作できexists()

2

を使用してチェックする必要はありません。

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

空の場合は空のリストを返し、そうでない場合はリストの最初の要素を返します。

関連する問題