Djangoでは、QuerySet
繰り返して結果を出力することがあるので、オブジェクトを数えるための最良のオプションは何ですか?len(qs)
またはqs.count()
?
(また、同じ反復でオブジェクトをカウントすることはオプションではありません。)
回答:
Djangoのドキュメントcount
では、len
次ではなく使用することを推奨していますが、
注:
len()
セット内のレコード数を決定するだけの場合は、QuerySetsで使用しないでください。SQLを使用してデータベースレベルでカウントを処理する方がはるかに効率的SELECT COUNT(*)
であり、Djangoはcount()
まさにこの理由でメソッドを提供します。
とにかくこのQuerySetを反復しているので、結果はキャッシュされます(を使用している場合を除くiterator
)。したがって、データベースに再度アクセスすることを回避し、異なる数の結果を取得する可能性もあるためlen
、を使用することをお勧めします!) 。
を使用している場合は、同じ理由で(countを使用するのではなく)反復するときにcounting変数を含めることをお勧めします。iterator
間を選択するlen()
とcount()
、状況に依存し、それが深く、彼らはそれらを正しく使用するためにどのように機能するかを理解する価値があります。
いくつかのシナリオを紹介します。
(最も重要)要素の数だけを知りたいが、それらを処理する予定がない場合は、次を使用することが重要count()
です。
実施: queryset.count()
-これは単一のSELECT COUNT(*) some_table
クエリを実行し、すべての計算はRDBMS側で実行され、PythonはO(1)の固定コストで結果番号を取得する必要があります
禁止事項: len(queryset)
-これはSELECT * FROM some_table
クエリを実行し、テーブル全体のO(N)をフェッチし、それを格納するために追加のO(N)メモリを必要とします。これはできる最悪の事態です
とにかく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
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ドキュメントの良い参考資料:
QuerySet
実装を状況に応じて投稿するための+1 。
テスト測定を好む人のために(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()
。
他の人がすでに答えたことを要約する:
len()
すべてのレコードをフェッチし、それらを繰り返し処理します。count()
SQL COUNT操作を実行します(大きなクエリセットを処理する場合ははるかに高速です)。この操作の後にクエリセット全体が繰り返される場合は、全体として、を使用する方がわずかに効率的である可能性があることも事実ですlen()
。
しかしながら
場合によっては、たとえばメモリ制限がある場合、実行された操作をレコードに対して分割すると便利な場合があります(可能な場合)。これは、djangoページネーションを使用して実現できます。
次に、を使用count()
することを選択すると、クエリセット全体を一度にフェッチする必要がなくなります。