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

просто использовать:

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

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


решения с 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.