В Django, какой самый эффективный способ проверить наличие пустого набора запросов? - PullRequest
16 голосов
/ 29 июля 2011

Я слышал предложения использовать следующее:

if qs.exists():
    ...

if qs.count():
    ...

try:
    qs[0]
except IndexError:
    ...

Скопировано из комментария ниже: «Я ищу утверждение типа« В MySQL и PostgreSQL count () быстрее для коротких запросов, существует () быстрее для длинных запросов и использует QuerySet [0], когда вероятно, что вам понадобится первый элемент, и вы хотите проверить, существует ли он. Однако, когда count () быстрее, он лишь незначительно быстрее, поэтому рекомендуется всегда использовать функцию there () при выборе между ними. "

Ответы [ 5 ]

15 голосов
/ 10 мая 2014

query.exists() - самый эффективный способ.

Особенно на postgres count() может быть очень дорогим, иногда дороже, чем обычный запрос на выборку.

exists() запускает запрос без select_related, выбора полей или сортировки и выбирает только одну запись. Это намного быстрее, чем считать весь запрос с помощью объединения таблиц и сортировки.

qs[0] все еще включает select_related, выбор полей и сортировку; так будет дороже.

Исходный код Django находится здесь (django / db / models / sql / query.py RawQuery.has_results):

https://github.com/django/django/blob/60e52a047e55bc4cd5a93a8bd4d07baed27e9a22/django/db/models/sql/query.py#L499

def has_results(self, using):
    q = self.clone()
    if not q.distinct:
        q.clear_select_clause()
    q.clear_ordering(True)
    q.set_limits(high=1)
    compiler = q.get_compiler(using=using)
    return compiler.has_results()

Еще один гоч, который получил меня на днях, вызывает QuerySet в операторе if. Это выполняет и возвращает весь запрос!

Если переменная query_set может быть None (не заданный аргумент для вашей функции), тогда используйте:

if query_set is None:
    # 

не:

if query_set:
   # you just hit the database
14 голосов
/ 03 августа 2011

Похоже, qs.count () и qs.exists () фактически эквивалентны .Поэтому я не обнаружил причину использовать существующие () над count ().Последний не медленнее, и его можно использовать для проверки существования и длины.Вполне возможно, что оба метода available () и count () соответствуют одному и тому же запросу в MySQL.

Используйте qs[0], только если вам действительно нужен объект.Это значительно медленнее, если вы просто тестируете на существование.

В Amazon SimpleDB 400 000 строк:

  • bare qs: 325,00 usec / pass
  • qs.exists(): 144,46 usec / pass
  • qs.count() 144.33 usec / pass
  • qs[0]: 324,98 usec / pass

В MySQL 57 строк:

  • bare qs: 1,07 usec / pass
  • qs.exists(): 1,21 usec / pass
  • qs.count(): 1,16 usec / pass
  • qs[0]: 1.27 usec / pass

Я использовал случайный запрос для каждого прохода, чтобы уменьшить риск кэширования на уровне базы данных.Тестовый код:

import timeit

base = """
import random
from plum.bacon.models import Session
ip_addr = str(random.randint(0,256))+'.'+str(random.randint(0,256))+'.'+str(random.randint(0,256))+'.'+str(random.randint(0,256))
try:
    session = Session.objects.filter(ip=ip_addr)%s
    if session:
        pass
except:
    pass
"""

query_variatons = [
    base % "",
    base  % ".exists()",
    base  % ".count()",
    base  % "[0]"
    ]

for s in query_variatons:
    t = timeit.Timer(stmt=s)
    print "%.2f usec/pass" % (1000000 * t.timeit(number=100)/100000)
7 голосов
/ 29 июля 2011

Зависит от контекста использования.

Согласно документации :

Использовать QuerySet.count ()

... если вам нужен только счет, а не len (queryset).

Использовать QuerySet.exists ()

... если вы хотите выяснить, существует ли хотя бы один результат, а не запросить его.

Но:

Не злоупотреблять count () и существует ()

Если вам понадобятся другие данные из QuerySet, просто оцените их.

Итак, я думаю, что QuerySet.exists() является наиболее рекомендуемым способом, если вы просто хотите проверить пустой QuerySet. С другой стороны, если вы хотите использовать результаты позже, лучше оценить их.

Я также думаю, что ваш третий вариант самый дорогой, потому что вам нужно получить все записи, чтобы проверить, существуют ли они.

4 голосов
/ 13 апреля 2017

@ Решение Сэма Одио послужило хорошей отправной точкой, но в методологии есть несколько недостатков, а именно:

  1. Случайный IP-адрес может в конечном итоге совпадать с 0 или очень малорезультаты
  2. Исключение может исказить результаты, поэтому мы должны стремиться избегать обработки исключений

Поэтому вместо того, чтобы фильтровать что-то, что может соответствовать, я решил исключить то, что определенно не будетсовпадение, надеюсь, по-прежнему избегая кеша БД, но также обеспечивая такое же количество строк.

Я тестировал только на локальной базе данных MySQL с набором данных:

>>> Session.objects.all().count()
40219

Код синхронизации:

import timeit
base = """
import random
import string
from django.contrib.sessions.models import Session
never_match = ''.join(random.choice(string.ascii_uppercase) for _ in range(10))
sessions = Session.objects.exclude(session_key=never_match){}
if sessions:
    pass
"""
s = base.format('count')

query_variations = [
    "",
    ".exists()",
    ".count()",
    "[0]",
]

for variation in query_variations:
    t = timeit.Timer(stmt=base.format(variation))
    print "{} => {:02f} usec/pass".format(variation.ljust(10), 1000000 * t.timeit(number=100)/100000)

выходы:

           => 1390.177710 usec/pass
.exists()  => 2.479579 usec/pass
.count()   => 22.426991 usec/pass
[0]        => 2.437079 usec/pass

Итак, вы можете видеть, что count() примерно в 9 раз медленнее, чем exists() для этого набора данных.

[0] isтакже быстро, но требует обработки исключений.

1 голос
/ 29 июля 2011

Я полагаю, что первый метод является наиболее эффективным (вы могли бы легко реализовать его в терминах второго метода, поэтому, возможно, они почти идентичны).Последний требует фактического получения целого объекта из базы данных, поэтому он почти наверняка самый дорогой.

Но, как и все эти вопросы, единственный способ узнать вашу конкретную базу данных, схему и набор данныхпроверить это самостоятельно.

...