Django查询集上的Count()与len()

lymnna71  于 2022-12-20  发布在  Go
关注(0)|答案(6)|浏览(138)

在Django中,假设我有一个QuerySet,我要迭代并打印结果,那么计算对象的最佳选项是什么?len(qs)还是qs.count()
(Also假定在同一迭代中计数对象不是一个选项)。

6l7fqoea

6l7fqoea1#

尽管Django docs建议使用count而不是len
注意:如果你只想确定集合中的记录数,就不要在QuerySets上使用len()。在数据库级别处理计数要高效得多,使用SQL的SELECT COUNT(*),Django提供了一个count()方法,正是出于这个原因。
由于您无论如何都要迭代此QuerySet,因此结果将被缓存(除非您使用的是iterator),因此最好使用len,因为这可以避免再次命中数据库,并且还可能检索不同数量的结果!)。
如果您使用的是iterator,那么出于同样的原因,我建议在迭代时包含一个计数变量(而不是使用count)。

cyvaqqii

cyvaqqii2#

len()count()之间进行选择取决于具体情况,因此有必要深入了解它们的工作原理以正确使用它们。
让我为您提供几个场景:
1.(最关键的)当你只想知道元素的数量,而不打算以任何方式处理它们时,使用count()是至关重要的:

    • DO:**queryset.count()-这将执行单个SELECT COUNT(*) FROM some_table查询,所有计算都在RDBMS端进行,Python只需要以O(1)的固定成本检索结果数
    • 不要:**len(queryset)-这将执行SELECT * FROM some_table查询,提取整个表的时间复杂度为O(N),并且需要额外的O(N)内存来存储它。这是最糟糕的情况

1.当您无论如何都要获取查询集时,使用len()稍微好一点,因为它不会像count()那样导致额外的数据库查询
len()(一个数据库查询)

len(queryset) # SELECT * 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

count()(两个数据库查询!):

queryset.count() # First db query SELECT COUNT(*)

    for obj in queryset: # Second db query (fetching data) SELECT *
        pass

1.已恢复第二种情况(已提取查询集时):

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时
  • 计数()
uemypmqf

uemypmqf3#

我认为使用len(qs)在这里更有意义,因为您需要迭代结果。如果您只想打印计数而不迭代结果,qs.count()是一个更好的选择。
len(qs)将使用select * from table命中数据库,而qs.count()将使用select count(*) from table命中数据库。
qs.count()给予返回整数,并且您不能对其进行迭代

eivgtgni

eivgtgni4#

对于喜欢测试测量的人(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()len()快了2倍呢?

b1uwtaje

b1uwtaje5#

总结其他人已经回答的问题:

  • len()将获取所有记录并对其进行迭代。
  • count()将执行SQL COUNT操作(处理大型查询集时要快得多)。

同样,如果在此操作之后,整个查询集将被迭代,那么作为整体,使用len()可能稍微更有效一些。

然而

在某些情况下,例如当内存有限制时,可以方便地(如果可能的话)拆分对记录执行的操作,这可以使用django pagination来实现。
然后,可以选择使用count(),这样就可以避免一次获取整个查询集。

2jcobegt

2jcobegt6#

我用count()len()试验了模型和原始查询获得1000万行的速度有多快。

实验结果

| | 计数()|透镜()|
| - ------| - ------| - ------|
| * * 模型查询**| * * 1.02秒**| * * 四十六点一三秒**|
| * * 原始查询**| * * 0.48秒**| * * 三点一六秒**|
因此,快速中的顺序如下:
1.使用原始查询的count()0.48秒

  1. count(),带模型查询(1.02秒
    1.使用原始查询的len()3.16秒
    1.带模型查询的len()46.13秒
    我建议基本上将count()与模型查询一起使用,因为它比len()更快,代码更少,比原始查询更方便,但当使用**select_for_update()**时,您应该将len()与模型查询一起使用,因为select_for_update()count()一起不起作用,而且代码更少,比原始查询更方便。

如何实验

首先,我创建了只有**idTest模型**:

# "store/models.py"

from django.db import models

class Test(models.Model):
    pass

然后,运行以下命令:

python manage.py makemigrations && python manage.py migrate

然后,使用psql一次向**store_test插入1000万行**:

postgres=# INSERT INTO store_test (id) SELECT generate_series(1, 10000000);
INSERT 0 10000000
Time: 29929.337 ms (00:29.929)

最后,我运行了test_view(),如下所示:

# "store/views.py"

from time import time
from .models import Test
from django.db import connection
from django.http import HttpResponse

def test_view(request):

    # "count()" with model query
    start = time()
    print(Test.objects.all().count(), "- count() - Model query")
    end = time()
    print(round(end - start, 2), "seconds\n")

    # "len()" with model query
    start = time()
    print(len(Test.objects.all()), "- len() - Model query")
    end = time()
    print(round(end - start, 2), "seconds\n")
    
    # "count()" with raw query
    start = time()
    with connection.cursor() as cursor:
        cursor.execute("SELECT count(*) FROM store_test;") 
        print(cursor.fetchone()[0], "- count() - Raw query")
    end = time()
    print(round(end - start, 2), "seconds\n")

    # "len()" with raw query
    start = time()
    with connection.cursor() as cursor:
        cursor.execute("SELECT * FROM store_test;")
        print(len(cursor.fetchall()), "- len() - Raw query") 
    end = time()
    print(round(end - start, 2), "seconds\n")
    
    return HttpResponse("Test_view")

控制台上的输出:

10000000 - count() - Model query
1.02 seconds

10000000 - len() - Model query
46.13 seconds

10000000 - count() - Raw query
0.48 seconds

10000000 - len() - Raw query
3.16 seconds

[18/Dec/2022 07:12:14] "GET /store/test_view/ HTTP/1.1" 200 9

相关问题