Граф против len на QuerySet Django - программирование
Подтвердить что ты не робот

Граф против len на QuerySet Django

В Django, учитывая, что у меня есть QuerySet который я собираюсь повторить и распечатать результаты, каков наилучший вариант для подсчета объектов? len(qs) или qs.count()?

(Также учитывая, что подсчет объектов в одной итерации не вариант.)

4b9b3361

Ответ 1

Хотя Django Docs рекомендует использовать count а не len:

Примечание. Не используйте len() в QuerySets, если все, что вам нужно, это определить количество записей в наборе. Намного эффективнее обрабатывать счет на уровне базы данных, используя SQL SELECT COUNT(*), и Django предоставляет метод count() именно по этой причине.

Так как вы все равно выполняете итерацию этого QuerySet, результат будет кэшироваться (если вы не используете iterator), и поэтому будет предпочтительно использовать len, так как это позволит избежать повторного попадания в базу данных, а также, возможно, получения другого количества результатов. !).
Если вы используете iterator, то я бы предложил включить подсчетную переменную во время итерации (а не использования счетчика) по тем же причинам.

Ответ 2

Выбор между len() и count() зависит от ситуации, и стоит глубоко понять, как они работают, чтобы правильно их использовать.

Позвольте мне представить вам несколько сценариев:

  1. (наиболее важно) Если вы хотите знать только количество элементов и не планируете обрабатывать их каким-либо образом, крайне важно использовать count():

    DO: queryset.count() - это будет выполнять один запрос SELECT COUNT(*) some_table, все вычисления выполняются на стороне СУБД, Python просто нужно получить номер результата с фиксированной стоимостью O (1)

    DON'T: 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. Вернулся второй случай (когда набор запросов уже был получен):

    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 документах:

Ответ 3

Я думаю, что использование len(qs) имеет больше смысла здесь, поскольку вам нужно перебирать результаты. qs.count() - лучший вариант, если все, что вы хотите сделать, распечатывает счетчик и не перебирает результаты.

len(qs) попадет в базу данных с помощью select * from table, тогда как qs.count() ударит по db с помощью select count(*) from table.

также qs.count() даст целое число return и вы не можете его перебирать