DjangoQuerySetでのカウントとlen


93

Djangoでは、QuerySet繰り返して結果を出力することがあるので、オブジェクトを数えるための最良のオプションは何ですか?len(qs)またはqs.count()

(また、同じ反復でオブジェクトをカウントすることはオプションではありません。)


2
興味深い質問です。これをプロファイリングすることをお勧めします。私は非常に興味があります!完全に評価されたオブジェクトのlen()に多くのオーバーヘッドがあるかどうかを知るには、Pythonについて十分に知りません。数えるより速いかもしれません!
ゆうじ '冨田'富田

回答:


132

Djangoのドキュメントcountでは、len次ではなく使用することを推奨していますが、

注:len()セット内のレコード数を決定するだけの場合は、QuerySetsで使用しないでください。SQLを使用してデータベースレベルでカウントを処理する方がはるかに効率的SELECT COUNT(*)であり、Djangoはcount()まさにこの理由でメソッドを提供します。

とにかくこのQuerySetを反復しているので、結果はキャッシュされます(を使用している場合を除くiterator)。したがって、データベースに再度アクセスすることを回避し、異なる数の結果を取得する可能性もあるためlen、を使用することをお勧めします!) 。 を使用している場合は、同じ理由で(countを使用するのではなく)反復するときにcounting変数を含めることをお勧めします。
iterator


60

間を選択するlen()count()、状況に依存し、それが深く、彼らはそれらを正しく使用するためにどのように機能するかを理解する価値があります。

いくつかのシナリオを紹介します。

  1. (最も重要)要素の数だけを知りたいが、それらを処理する予定がない場合は、次を使用することが重要count()です。

    実施: queryset.count() -これは単一のSELECT COUNT(*) some_tableクエリを実行し、すべての計算はRDBMS側で実行され、PythonはO(1)の固定コストで結果番号を取得する必要があります

    禁止事項: len(queryset) -これはSELECT * FROM some_tableクエリを実行し、テーブル全体のO(N)をフェッチし、それを格納するために追加のO(N)メモリを必要とします。これはできる最悪の事態です

  2. とにかくlen()クエリセットをフェッチする場合は、次のように余分なデータベースクエリが発生しないように使用することをお勧めしますcount()

    len(queryset) # fetching all the data - NO extra cost - data would be fetched anyway in the for loop
    
    for obj in queryset: # data is already fetched by len() - using cache
        pass
    

    カウント:

    queryset.count() # this will perform an extra db query - len() did not
    
    for obj in queryset: # fetching data
        pass
    
  3. 2番目のケースを元に戻しました(クエリセットがすでにフェッチされている場合):

    for obj in queryset: # iteration fetches the data
        len(queryset) # using already cached data - O(1) no extra cost
        queryset.count() # using cache - O(1) no extra db query
    
    len(queryset) # the same O(1)
    queryset.count() # the same: no query, O(1)
    

「ボンネットの下」を一目見れば、すべてが明確になります。

class QuerySet(object):

    def __init__(self, model=None, query=None, using=None, hints=None):
        # (...)
        self._result_cache = None

    def __len__(self):
        self._fetch_all()
        return len(self._result_cache)

    def _fetch_all(self):
        if self._result_cache is None:
            self._result_cache = list(self.iterator())
        if self._prefetch_related_lookups and not self._prefetch_done:
            self._prefetch_related_objects()

    def count(self):
        if self._result_cache is not None:
            return len(self._result_cache)

        return self.query.get_count(using=self.db)

Djangoドキュメントの良い参考資料:


5
素晴らしい答え、QuerySet実装を状況に応じて投稿するための+1 。
nehem

4
文字通り完璧な答えです。何を使用するか、そしてさらに重要なことに、使用の理由も説明します。
トムPegler

28

len(qs)結果を繰り返す必要があるので、ここでは使用する方が理にかなっていると思います。qs.count()やりたいことがすべてカウントを出力し、結果を繰り返さない場合は、より適切なオプションです。

len(qs)でデータベースをヒットするselect * from table一方qs.count()で、DBにヒットしますselect count(*) from table

またqs.count()、戻り整数を返し、それを繰り返すことはできません


3

テスト測定を好む人のために(Postresql):

単純なPersonモデルとその1000個のインスタンスがある場合:

class Person(models.Model):
    name = models.CharField(max_length=100)
    age = models.SmallIntegerField()

    def __str__(self):
        return self.name

平均的な場合、次のようになります。

In [1]: persons = Person.objects.all()

In [2]: %timeit len(persons)                                                                                                                                                          
325 ns ± 3.09 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [3]: %timeit persons.count()                                                                                                                                                       
170 ns ± 0.572 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

では、この特定のテストケースよりもcount()2倍速くどのように見ることができますかlen()


0

他の人がすでに答えたことを要約する:

  • len() すべてのレコードをフェッチし、それらを繰り返し処理します。
  • count() SQL COUNT操作を実行します(大きなクエリセットを処理する場合ははるかに高速です)。

この操作の後にクエリセット全体が繰り返される場合は、全体として、を使用する方がわずかに効率的である可能性があることも事実ですlen()

しかしながら

場合によっては、たとえばメモリ制限がある場合、実行された操作をレコードに対して分割すると便利な場合があります(可能な場合)。これは、djangoページネーションを使用して実現できます。

次に、を使用count()することを選択すると、クエリセット全体を一度にフェッチする必要がなくなります。

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