djangoのクエリセットから最初のオブジェクトを取得する最速の方法は?


193

DjangoのNoneクエリセットから最初のオブジェクトを取得するか、何もない場合は戻りたいことがよくあります。これを行う方法はたくさんありますが、すべてうまくいきます。しかし、私はどちらが最もパフォーマンスが良いのかと思っています。

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テストだけである場合に、非常にメモリを集中的に使用することです。

これを行うには、1回のデータベース呼び出しだけで、例外オブジェクトを使用してメモリをチャーンしないでください。


21
経験則:DBラウンドトリップの最小化が心配な場合はlen()、クエリセットでは使用せず、常にを使用してください.count()
ダニエルディパオロ

7
「多くの場合、例外オブジェクトを作成します。これは非常にメモリを集中的に使用します」-1つの追加の例外を作成することに不安がある場合、Pythonはあらゆる場所で例外を使用するため、間違って実行しています。あなたの場合、メモリを大量に消費することを実際にベンチマークしましたか?
lqc

1
@Leopdそして、何らかの方法(または少なくともコメント)で実際にanwserのベンチマークを行った場合、それが速くなることはありません。それは実際に遅いかもしれません、なぜならあなたがそれを捨てるために余分なリストを作成するからです。そもそも、Python関数を呼び出すコストや、DjangoのORMを使用するコストと比較すると、それはすべてピーナッツです。filter()への1回の呼び出しは、例外を発生させるよりもずっと遅く、倍も遅くなります(イテレータプロトコルが動作する方法であるため、発生する可能性があります!)。
lqc 2012

1
パフォーマンスの違いは小さいというあなたの直感は正しいですが、あなたの結論は間違っています。私はベンチマークを実行しましたが、受け入れられた答えは実際には実際のマージンよりも高速です。図を行きます。
Leopd 2012

11
Djangoの1.6を使用して人々のために、彼らは最終的に追加したfirst()last():便利なメソッドdocs.djangoproject.com/en/dev/ref/models/querysets/#first
魏円

回答:


327

(2013年11月発売)のDjango 1.6導入便利な方法を first()last()その結果として生じる例外リターンを飲み込むNoneクエリセットは何のオブジェクトが返されない場合。


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

13
また、first()およびlast()強制ORDER BYクエリの句を。結果は確定的になりますが、おそらくクエリの速度が低下します。
Phil Krylov 2017年

@ janek37パフォーマンスに違いはありません。cod3monk3yが示すように、これは便利な方法であり、クエリセット全体を読み取るわけではありません。
Zompa

142

正解は

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

以下で使用できます:

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

すべてのレコードの完全なデータベース呼び出しを強制するため、最初にそれをリストにしたくないでしょう。上記を実行するだけで、最初のプルのみが行われます。あなたはあなたがあなたが.order_by望む最初のものを確実にするために使うことさえできます。

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


9
元の3番目のオプションに似ていますがスライスを使用するObjectDoesNotExistを除いて、それを試してラップする必要があります。
ダニーW.アデア2012年

1
最後にget()を呼び出す場合、LIMITを設定する意味は何ですか?ORMとSQLコンパイラーがバックエンドに最適なものを決定するようにします(たとえば、Oracle DjangoではLIMITをエミュレートするため、助ける代わりに害を及ぼします)。
lqc

末尾の.get()なしでこの回答を使用しました。リストが返された場合は、リストの最初の要素を返します。
キースジョンハッチソン2013

持つことの違いは何Entry.objects.all()[0]ですか?
James Lin

15
@JamesLin違いは、[:1] .get()はDoesNotExistを発生させ、[0]はIndexErrorを発生させることです。
Ropez 2013

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

1
トレースをオンにするLIMIT 1と、クエリにこれが追加されることも確認できます。これ以上のことができるとは思いません。ただし、内部__nonzero__QuerySet実装されtry: iter(self).next() except StopIteration: return false...ているため、例外をエスケープしません。
ベンジャクソン

@Ben:QuerySet.__nonzero__()が真かどうかを確認QuerySetするlist前にに変換されるため、呼び出されることはありません。ただし、他の例外が引き続き発生する場合があります。
Ignacio Vazquez-Abrams

@Aron:StopIteration例外が発生する可能性があります。
Ignacio Vazquez-Abrams

リストへの変換===呼び出し__iter__は、新しいイテレータオブジェクトを取得し、nextそれStopIterationがスローされるまでそのメソッドを呼び出します したがって、
間違い

14
この回答は古くなっています。Django1.6以降の@ cod3monk3yの回答を
ご覧ください

37

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

YourModel.objects.all().first()

これはクエリセットが空の場合に例外をスローしないため、.get()またはより良い方法です。[0]exists()


1
これにより、SQLでLIMIT 1が発生し、クエリが遅くなる可能性があるという主張を確認しました。ただし、それが実証されていることを確認したいのですが、クエリが1つのアイテムしか返さない場合、LIMIT 1が本当にパフォーマンスに影響を与えるのはなぜですか?だから私は上記の答えは良いと思いますが、証拠が確認するのを見てみたいです。
rrauenza

「良い」とは言えません。それは本当にあなたの期待次第です。
トライグラス

7

最初の要素を頻繁に取得する予定の場合、QuerySetをこの方向に拡張できます。

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


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

次のようにモデルを定義します。

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

次のように使用します。

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

オブジェクトを呼び出す= ManagerWithFirstQuery asオブジェクト= ManagerWithFirstQuery()-括弧を忘れない-とにかく、あなたは私を助けた+1
Kamil

7

これもうまくいくかもしれません:

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

空の場合は空のリストを返し、それ以外の場合はリスト内の最初の要素を返します。


1
これは、群を抜いて最良の解決策です...データベースへの1回の呼び出しのみで結果が得られます
Shh

5

それはこのようにすることができます

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

または

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

3

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

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

1
ただし、私がそれを正しく理解している場合を除き、慣用的なPythonでは、通常、Look Before You Leapアプローチではなく、パーミッションよりも許しを求めやすいEAFP)アプローチを使用します。
BigSmoke 2016年

EAFPは単なるスタイルの推奨事項ではなく、理由があります(たとえば、ファイルを開く前にチェックしてもエラーを防ぐことはできません)。ここで関連する考慮事項は、存在する+アイテムを取得すると2つのデータベースクエリが発生することであると思います。これは、プロジェクトやビューによっては望ましくない場合があります。
エリックアラウージョ

2

django 1.6以降では、次のようにfirst()メソッドでfilter()を使用できます。

Model.objects.filter(field_name=some_param).first()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.