Как вытащить случайную запись с помощью Django ORM?
У меня есть модель, которая представляет картины, которые я представляю на своем сайте. На главной веб-странице я хотел бы показать некоторые из них: новейшие, которые не были посещены в большинстве случаев, самые популярные и случайные.
Я использую Django 1.0.2.
В то время как первые 3 из них легко вытягиваются с использованием моделей django, последний (случайный) вызывает у меня некоторые проблемы. Я могу из кода его на мой взгляд, что-то вроде этого:
number_of_records = models.Painting.objects.count()
random_index = int(random.random()*number_of_records)+1
random_paint = models.Painting.get(pk = random_index)
Это не похоже на то, что я хотел бы иметь на мой взгляд. Это - полностью часть абстракции базы данных и должна быть в модели. Кроме того, здесь мне нужно позаботиться об удаленных записях (тогда число всех записей не покрывает меня всеми возможными значениями ключа) и, вероятно, много других вещей.
Любые другие варианты, как я могу это сделать, желательно как-то внутри абстракции модели?
Ответы
Ответ 1
Использование order_by('?')
приведет к удалению сервера db во второй день производства. Лучший способ - это то, что описано в Получение случайной строки из реляционной базы данных.
from django.db.models.aggregates import Count
from random import randint
class PaintingManager(models.Manager):
def random(self):
count = self.aggregate(count=Count('id'))['count']
random_index = randint(0, count - 1)
return self.all()[random_index]
Ответ 2
Просто используйте:
MyModel.objects.order_by('?').first()
Документировано в API QuerySet.
Ответ 3
Решения с order_by ('?') [: N] чрезвычайно медленны даже для таблиц среднего размера, если вы используете MySQL (не знаете о других базах данных).
order_by('?')[:N]
будет переведен в запрос SELECT ... FROM ... WHERE ... ORDER BY RAND() LIMIT N
.
Это означает, что для каждой строки таблицы будет выполняться функция RAND(), тогда вся таблица будет сортироваться в соответствии со значением этой функции, а затем будут возвращены первые N записей. Если ваши таблицы маленькие, это нормально. Но в большинстве случаев это очень медленный запрос.
Я написал простую функцию, которая работает, даже если у id есть отверстия (некоторые строки, где они удалены):
def get_random_item(model, max_id=None):
if max_id is None:
max_id = model.objects.aggregate(Max('id')).values()[0]
min_id = math.ceil(max_id*random.random())
return model.objects.filter(id__gte=min_id)[0]
Он работает быстрее, чем order_by ('?') почти во всех случаях.
Ответ 4
Вы можете создать менеджера в своей модели, чтобы делать подобные вещи. Чтобы сначала понять, что такое менеджер, метод Painting.objects
- это менеджер, который содержит all()
, filter()
, get()
и т.д. Создание собственного менеджера позволяет предварительно фильтровать результаты и использовать все те же методы, а также ваши собственные методы работы с результатами.
РЕДАКТИРОВАТЬ: я изменил свой код, чтобы отразить метод order_by['?']
. Обратите внимание, что менеджер возвращает неограниченное количество случайных моделей. Из-за этого я включил немного кода использования, чтобы показать, как получить только одну модель.
from django.db import models
class RandomManager(models.Manager):
def get_query_set(self):
return super(RandomManager, self).get_query_set().order_by('?')
class Painting(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
objects = models.Manager() # The default manager.
randoms = RandomManager() # The random-specific manager.
использование
random_painting = Painting.randoms.all()[0]
Наконец, у вас может быть много менеджеров в ваших моделях, поэтому не стесняйтесь создавать LeastViewsManager()
или MostPopularManager()
.
Ответ 5
Здесь простое решение:
from random import randint
count = Model.objects.count()
random_object = Model.objects.all()[randint(0, count - 1)] #single random object
Ответ 6
Другие ответы либо потенциально медленны (используя order_by('?')
), либо используют более одного SQL-запроса. Здесь примерное решение без упорядочения и только один запрос (при условии Postgres):
Model.objects.raw('''
select * from {0} limit 1
offset floor(random() * (select count(*) from {0}))
'''.format(Model._meta.db_table))[0]
Помните, что это приведет к ошибке индекса, если таблица пуста. Напишите себе вспомогательную функцию model-agnostic, чтобы проверить это.
Ответ 7
Просто простую идею, как я это делаю:
def _get_random_service(self, professional):
services = Service.objects.filter(professional=professional)
i = randint(0, services.count()-1)
return services[i]
Ответ 8
Я создал модель менеджера
models.py (пример)
from django.db import models
class RandomManager(models.Manager):
def get_random(self, items=1):
'''
items is integer value
By default it returns 1 random item
'''
if isinstance(items, int):
return self.model.objects.order_by('?')[:items]
return self.all()
class Category(models.Model):
name = models.CharField(max_length=100)
objects = RandomManager()
class Meta:
default_related_name = 'categories'
verbose_name = 'category'
verbose_name_plural = 'categories'
И вы можете получить случайные предметы из базы данных, например
Category.objects.get_random(5) # To get 5 random items
Ответ 9
Это высоко рекомендуется Получение случайной строки из реляционной базы данных
Поскольку использование django orm для выполнения подобной вещи сделает ваш сервер db сердитым специально, если у вас есть большая таблица данных: |
И решение предоставляет диспетчер моделей и записывает запрос SQL вручную;)
Обновление
Другое решение, которое работает на любом бэкэнде базы данных, даже не относящихся к нему, без написания пользовательского ModelManager
. Получение случайных объектов из Queryset в Django
Ответ 10
Просто отметим (довольно распространенный) специальный случай, если в таблице есть индексированный столбец автоинкремента, без удаления, оптимальным способом случайного выбора является запрос типа:
SELECT * FROM table WHERE id = RAND() LIMIT 1
который принимает такой столбец с именем id для таблицы. В django вы можете сделать это:
Painting.objects.raw('SELECT * FROM appname_painting WHERE id = RAND() LIMIT 1')
в котором вы должны заменить appname своим именем приложения.
В целом, с столбцом id, order_by ('?') может выполняться намного быстрее:
Paiting.objects.raw(
'SELECT * FROM auth_user WHERE id>=RAND() * (SELECT MAX(id) FROM auth_user) LIMIT %d'
% needed_count)
Ответ 11
Вы можете использовать тот же подход, который вы бы использовали для отбора любого итератора, особенно если вы планируете пробовать несколько элементов для создания набора образцов. @MatijnPieters и @DzinX много размышляли над этим:
def random_sampling(qs, N=1):
"""Sample any iterable (like a Django QuerySet) to retrieve N random elements
Arguments:
qs (iterable): Any iterable (like a Django QuerySet)
N (int): Number of samples to retrieve at random from the iterable
References:
@DZinX: /questions/44847/python-random-sample-with-a-generator-iterable-iterator/320738#320738
@MartinPieters: https://stackoverflow.com/a/12581484/623735
"""
samples = []
iterator = iter(qs)
# Get the first `N` elements and put them in your results list to preallocate memory
try:
for _ in xrange(N):
samples.append(iterator.next())
except StopIteration:
raise ValueError("N, the number of reuested samples, is larger than the length of the iterable.")
random.shuffle(samples) # Randomize your list of N objects
# Now replace each element by a truly random sample
for i, v in enumerate(qs, N):
r = random.randint(0, i)
if r < N:
samples[r] = v # at a decreasing rate, replace random items
return samples
Ответ 12
Один более простой подход к этому заключается в простом фильтрации до интересующего набора записей и использовании random.sample
, чтобы выбрать столько, сколько вы хотите:
from myapp.models import MyModel
import random
my_queryset = MyModel.objects.filter(criteria=True) # Returns a QuerySet
my_object = random.sample(my_queryset, 1) # get a single random element from my_queryset
my_objects = random.sample(my_queryset, 5) # get five random elements from my_queryset
Обратите внимание, что у вас должен быть код для проверки того, что my_queryset
не пуст; random.sample
возвращает ValueError: sample larger than population
, если первый аргумент содержит слишком мало элементов.
Ответ 13
Привет, мне нужно было выбрать случайную запись из набора запросов, длина которой мне также нужно было сообщить (т.е. Веб-страница произвела описанный элемент и оставила записи)
q = Entity.objects.filter(attribute_value='this or that')
item_count = q.count()
random_item = q[random.randomint(1,item_count+1)]
потребовалось вдвое меньше (0,7 с против 1,7 с), как:
item_count = q.count()
random_item = random.choice(q)
Я предполагаю, что это избегает сносить весь запрос перед выбором случайной записи и делает мою систему достаточно отзывчивой для страницы, к которой обращаются неоднократно для повторяющейся задачи, где пользователи хотят видеть обратный отсчет item_count.
Ответ 14
Я получил очень простое решение, сделать собственный менеджер:
class RandomManager(models.Manager):
def random(self):
return random.choice(self.all())
а затем добавить в модель:
class Example(models.Model):
name = models.CharField(max_length=128)
objects = RandomManager()
Теперь вы можете использовать его:
Example.objects.random()
Ответ 15
Что не так просто:
import random
records = Model.objects.all()
random_record = random.choice(records)
Ответ 16
Метод автоматического увеличения первичного ключа без удалений
Если у вас есть таблица, в которой первичный ключ представляет собой последовательное целое число без пробелов, тогда должен работать следующий метод:
import random
max_id = MyModel.objects.last().id
random_id = random.randint(0, max_id)
random_obj = MyModel.objects.get(pk=random_id)
Этот метод гораздо более эффективен, чем другие методы, которые выполняют итерацию по всем строкам таблицы. Хотя это требует двух запросов к базе данных, оба тривиальны. Кроме того, это просто и не требует определения дополнительных классов. Однако его применимость ограничена таблицами с автоинкрементным первичным ключом, где строки никогда не удаляются, так что в последовательности идентификаторов нет пробелов.
В случае, когда строки были удалены, такие как пробелы, этот метод все еще может работать, если он повторяется до случайного выбора существующего первичного ключа.
Рекомендации