Как вытащить случайную запись с помощью 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)

Этот метод гораздо более эффективен, чем другие методы, которые выполняют итерацию по всем строкам таблицы. Хотя это требует двух запросов к базе данных, оба тривиальны. Кроме того, это просто и не требует определения дополнительных классов. Однако его применимость ограничена таблицами с автоинкрементным первичным ключом, где строки никогда не удаляются, так что в последовательности идентификаторов нет пробелов.

В случае, когда строки были удалены, такие как пробелы, этот метод все еще может работать, если он повторяется до случайного выбора существующего первичного ключа.

Рекомендации