Как получить случайную запись, используя ORM Джанго? - PullRequest
164 голосов
/ 07 июня 2009

У меня есть модель, которая представляет картины, которые я представляю на своем сайте. На главной веб-странице я хотел бы показать некоторые из них: новейшую, ту, которую не посещали большую часть времени, самую популярную и случайную.

Я использую 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)

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

Любые другие варианты, как я могу это сделать, желательно как-то внутри абстракции модели?

Ответы [ 15 ]

238 голосов
/ 07 июня 2009

Просто используйте:

MyModel.objects.order_by('?').first()

Это задокументировано в QuerySet API .

152 голосов
/ 22 января 2010

Использование 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]
26 голосов
/ 09 июня 2009

Решения с 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 ('?') Почти во всех случаях.

10 голосов
/ 03 мая 2017

Вот простое решение:

from random import randint

count = Model.objects.count()
random_object = Model.objects.all()[randint(0, count - 1)] #single random object
10 голосов
/ 07 июня 2009

Вы можете создать менеджер на вашей модели, чтобы делать подобные вещи. Чтобы сначала понять, что такое менеджер, метод 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 голосов
/ 12 сентября 2015

Другие ответы могут быть либо медленными (с использованием 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]

Имейте в виду, что это приведет к ошибке индекса, если таблица пуста. Напишите себе вспомогательную функцию, независимую от модели, чтобы проверить это.

2 голосов
/ 03 июля 2018

Я создал модель менеджера

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 
2 голосов
/ 09 июня 2015

Просто простая идея, как мне это сделать:

def _get_random_service(self, professional):
    services = Service.objects.filter(professional=professional)
    i = randint(0, services.count()-1)
    return services[i]
1 голос
/ 28 марта 2019

Я получил очень простое решение, сделай свой менеджер:

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()
1 голос
/ 12 июня 2018

Привет! Мне нужно было выбрать случайную запись из набора запросов, длину которой я также должен был сообщить (т. Е. Веб-страница произвела описанный элемент и оставила указанные записи)

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.

...