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 - автоинкрементные целые числа, или не было слишком много удалений.