• en
  • Language: ru
  • Documentation version: 2.0

16. Как эффективно выбрать случайный объект из модели?

Ваши модели category выглядят следующим образом.

class Category(models.Model):
    name = models.CharField(max_length=100)

    class Meta:
        verbose_name_plural = "Categories"

    def __str__(self):
        return self.name

Вы хотите получить случайную Категорию. Мы рассмотрим несколько альтернативных способов сделать это.

Самый простой способ, вы можете order_by random и получить первую запись. Это будет выглядеть примерно так.

def get_random():
    return Category.objects.order_by("?").first()

Примечание: order_by('?') запросы могут быть дорогими и медленными, в зависимости от используемого бэкенда базы данных. Чтобы проверить другие методы, нам нужно вставить один миллион записей в таблицу Category. Зайдите в свою базу данных как в python manage.py dbshell и выполните следующее.

INSERT INTO entities_category
            (name)
(SELECT Md5(Random() :: text) AS descr
 FROM   generate_series(1, 1000000));

Вам не нужно понимать все детали sql выше, он создает один миллион чисел и md5-s их для создания имени, затем вставляет его в БД.

Теперь, вместо сортировки всей таблицы, вы можете получить максимальный id, сгенерировать случайное число в диапазоне [1, max_id] и отфильтровать его. Вы предполагаете, что удалений не было.

In [1]: from django.db.models import Max

In [2]: from entities.models import Category

In [3]: import random

In [4]: def get_random2():
   ...:     max_id = Category.objects.all().aggregate(max_id=Max("id"))['max_id']
   ...:     pk = random.randint(1, max_id)
   ...:     return Category.objects.get(pk=pk)
   ...:

In [5]: get_random2()
Out[5]: <Category: e2c3a10d3e9c46788833c4ece2a418e2>

In [6]: get_random2()
Out[6]: <Category: f164ad0c5bc8300b469d1c428a514cc1>

Если в вашей модели есть удаления, вы можете слегка модифицировать функции, чтобы зацикливать их до получения правильного Category.

In [8]: def get_random3():
   ...:     max_id = Category.objects.all().aggregate(max_id=Max("id"))['max_id']
   ...:     while True:
   ...:         pk = random.randint(1, max_id)
   ...:         category = Category.objects.filter(pk=pk).first()
   ...:         if category:
   ...:             return category
   ...:

In [9]: get_random3()
Out[9]: <Category: 334aa9926bd65dc0f9dd4fc86ce42e75>

In [10]: get_random3()
Out[10]: <Category: 4092762909c2c034e90c3d2eb5a73447>

Если в вашей модели нет большого количества удалений, цикл while True: возвращается быстро. Давайте воспользуемся timeit, чтобы увидеть различия.

In [14]: timeit.timeit(get_random3, number=100)
Out[14]: 0.20055226399563253

In [15]: timeit.timeit(get_random, number=100)
Out[15]: 56.92513192095794

get_random3 примерно в 283 раза быстрее, чем get_random. get_random - самый универсальный способ, но техника в get_random3 будет работать, если только вы не изменили стандартный способ, которым Django генерирует id - автоинкрементные целые числа, или не было слишком много удалений.