Как вытащить случайную запись с помощью ORM Django?
У меня есть модель, которая представляет картины, которые я представляю на своем сайте. На главной веб-странице Я хотел бы показать некоторые из них: новейший, тот, который не посещался в течение большинства времени, самый популярный и случайный.
Я использую Django 1.0.2.
в то время как первые 3 из них легко вытащить с помощью моделей django, последний (случайный) вызывает у меня некоторые проблемы. Я могу OFC кодировать его, на мой взгляд, примерно так:
number_of_records = models.Painting.objects.count()
random_index = int(random.random()*number_of_records)+1
random_paint = models.Painting.get(pk = random_index)
Это не похоже на то, что я хочу на мой взгляд, tho - это полностью часть абстракции базы данных и должно быть в модели. Кроме того, здесь мне нужно позаботиться об удаленных записях (тогда количество всех записей не будет покрывать все возможные ключевые значения) и, вероятно, много других вещей.
любые другие варианты, как я могу это сделать, желательно как-то внутри абстракции модели?
13 ответов
используя order_by('?')
убьет сервер БД на второй день в производстве. Лучший способ-это что-то вроде того, что описано в получение случайной строки из реляционной базы данных.
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]
решения с 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('?почти во всех случаях.
вы можете создать менеджер на вашей модели, чтобы делать такие вещи. Чтобы сначала понять, что такое менеджер,Painting.objects
метод-это менеджер, который содержит all()
, filter()
, get()
, etc. Создание собственного менеджера позволяет предварительно фильтровать результаты и использовать все эти методы, а также собственные пользовательские методы для работы с результатами.
редактировать: я изменил свой код, чтобы отразить 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()
.
вот простое решение:
from random import randint
count = Model.objects.count()
random_object = Model.objects.all()[randint(0, count - 1)] #single random object
другие ответы либо потенциально медленные (используя 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]
имейте в виду, что это вызовет ошибку индекса, если таблица пуста. Напишите себе вспомогательную функцию модели-агностика, чтобы проверить это.
Это очень рекомендуется получение случайной строки из реляционной базы данных
потому что использование django orm для таких вещей, как это, сделает ваш сервер БД злым, особенно если у вас есть таблица больших данных:/
и решение-предоставить диспетчер моделей и написать SQL-запрос вручную;)
обновление:
другое решение, которое работает на любом бэкэнде базы данных, даже без rel пишу на заказ ModelManager
. получение случайных объектов из Queryset в Django
просто простая идея, как я это делаю:
def _get_random_service(self, professional):
services = Service.objects.filter(professional=professional)
i = randint(0, services.count()-1)
return services[i]
вы можете использовать подход который вы бы использовали для выборки любого итератора, особенно если вы планируете попробовать несколько элементов для создания пример. @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: https://stackoverflow.com/a/12583436/623735
@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
один гораздо более простой подход к этому включает в себя просто фильтрацию до интересующего набора записей и использование 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
если первый аргумент содержит слишком много элементов.
например, если вы хотите получить 10 случайных элементов из модели. Вы можете использовать случайный модуль
import random
random_items = random.sample(list(model.objects.all()), k=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)
Привет, мне нужно было выбрать случайную запись из queryset, длина которого мне также нужно было сообщить (т. е. веб-страница произвела описанный элемент и сказала, что записи слева)
q = Entity.objects.filter(attribute_value='this or that')
item_count = q.count()
random_item = q[random.randomint(1,item_count+1)]
занял вдвое меньше времени(0.7 s против 1.7), что:
item_count = q.count()
random_item = random.choice(q)
Я предполагаю, что он избегает вытягивания всего запроса перед выбором случайной записи и сделал мою систему достаточно отзывчивой для страницы, к которой неоднократно обращаются для повторяющейся задачи, где пользователи хотят видеть счетчик item_count.