如何使用Django的ORM提取随机记录?

时间:2009-06-07 19:25:16

标签: python django django-models

我有一个模特代表我在网站上展示的画作。在主要网页上,我想展示一些:最新的,大多数时间没有访问过的,最受欢迎的一个和随机的一个。

我正在使用Django 1.0.2。

虽然前三个很容易使用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)

在我看来,它看起来不像我想要的东西 - 这完全是数据库抽象的一部分,应该在模型中。此外,在这里我需要处理已删除的记录(然后所有记录的数量不会覆盖我所有可能的键值)以及可能还有很多其他内容。

我可以做任何其他选择,最好以某种方式在模型抽象中?

16 个答案:

答案 0 :(得分:237)

只需使用:

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

记录在QuerySet API

答案 1 :(得分:151)

使用order_by('?')将在生产的第二天终止数据库服务器。更好的方法就像Getting a random row from a relational database中描述的那样。

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 :(得分:26)

如果使用MySQL(不了解其他数据库),即使对于中型表,order_by('?')[:N]的解决方案也非常慢。

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('?')快。

答案 3 :(得分:10)

您可以在模型上创建manager来执行此类操作。要首先了解经理是什么,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()

答案 4 :(得分:10)

这是一个简单的解决方案:

from random import randint

count = Model.objects.count()
random_object = Model.objects.all()[randint(0, count - 1)] #single random object

答案 5 :(得分:5)

其他答案可能很慢(使用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]

请注意,如果表为空,这将引发索引错误。给自己写一个与模型无关的辅助函数来检查它。

答案 6 :(得分:2)

我只是一个简单的想法:

def _get_random_service(self, professional):
    services = Service.objects.filter(professional=professional)
    i = randint(0, services.count()-1)
    return services[i]

答案 7 :(得分:2)

我创建了模型管理器

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 

答案 8 :(得分:2)

DB中的随机化在python中令人讨厌并且更好。但是同时,将所有数据从数据库带到python内存只是忽略大部分结果(尤其是在生产环境中)并不是一个好主意。我们可能还需要某种过滤。

  1. 所以基本上我们在DB上有数据,
  2. 我们想使用python的rand函数
  3. 和后记从数据库中弹出所有必需的数据。

基本上,使用2个查询比在DB CPU中随机选择(在DB中计算)或加载整个数据(繁重的网络利用率)要便宜得多。解释的解决方案必须具有可伸缩的性质,试图在此处进行计划,尤其是对于带有过滤器,软/硬删除甚至带有is_public标志的生产环境,将无法正常工作。因为我们生成的随机ID可能会从数据库中删除或在过滤器中被删减。假设max_id(records)== count(records)是一个坏习惯。

(当然,如果您不删除与查询使用的数据相当的百分比,或者您不想使用任何种类的过滤器,并且如果您有信心,可以使用random id,那么可以继续使用random)

如果您只想要一项。 请参阅(@Valter Silva)

import random

mgr = models.Painting.objects
qs = mgr.filter(...)
random_id = random.choice(1, qs.count())-1        # <--- [ First Query Hit ]

random_paint = qs[random_id] ## <-- [ Second Query Hit ]

如果您想要'n'个项目。

import random

req_no_of_random_items = 8        ## i need 8 random items.
qs = models.Painting.objects.filter(...)

## if u prefer to use random values often, you can keep this in cache. 
possible_ids = list(qs.values_list('id', flat=True))        # <--- [ First Query Hit ]

possible_ids = random.choices(possible_ids, k=8)
random_paint = qs.filter(pk__in=possible_ids) ## in a generic case to get 'n' items.

或者如果您想为生产使用更优化的代码,请使用缓存功能获取产品ID:

from django.core.cache import cache

def id_set_cache(qs):
    key = "some_random_key_for_cache"
    id_set =  cache.get(key)
    if id_set is None:
        id_set = list(qs.values_list('id', flat=True)
        cache.set(key, id_set)
    retrun id_set

答案 9 :(得分:1)

这是高度推荐的 Getting a random row from a relational database

因为使用django orm来做这样的事情,如果你有大数据表会使你的数据库服务器特别生气:|

解决方案是提供模型管理器并手动编写SQL查询;)

<强>更新

另一种解决方案适用于任何数据库后端甚至非相关的后端,而无需编写自定义ModelManagerGetting Random objects from a Queryset in Django

答案 10 :(得分:1)

注意一个(相当常见的)特殊情况,如果表中没有删除的索引自动增量列,则进行随机选择的最佳方法是查询:

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 :(得分:1)

您可能希望使用用于对任何迭代器进行采样的same approach,尤其是当您计划对多个项目进行采样以创建样本集时。 @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

答案 12 :(得分:1)

更简单的一种方法是简单地过滤到感兴趣的记录集,并使用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 :(得分:1)

您好我需要从查询集中选择一个随机记录,其长度我还需要报告(即网页制作描述的项目和所述记录留下)

q = Entity.objects.filter(attribute_value='this or that')
item_count = q.count()
random_item = q[random.randomint(1,item_count+1)]

花了一半的时间(0.7秒vs 1.7s):

item_count = q.count()
random_item = random.choice(q)

我猜测它可以避免在选择随机条目之前拉下整个查询,并使我的系统对于重复访问的页面响应足够,以便用户希望看到item_count倒计时。

答案 14 :(得分:1)

我有一个非常简单的解决方案,请定制经理:

class RandomManager(models.Manager):
    def random(self):
        return choice(self.all())

,然后添加模型:

class Example(models.Model):
    name = models.CharField(max_length=128)
    objects = RandomManager()

现在,您可以使用它:

Example.objects.random()

答案 15 :(得分:0)

无需删除即可自动递增主键的方法

如果您有一个表,其中主键是一个没有间隔的连续整数,那么以下方法应该有效:

import random
max_id = MyModel.objects.last().id
random_id = random.randint(0, max_id)
random_obj = MyModel.objects.get(pk=random_id)

此方法比其他遍历表所有行的方法效率更高。尽管确实需要两个数据库查询,但两者都很简单。此外,它很简单,不需要定义任何额外的类。但是,它的适用范围仅限于具有自动递增主键的表,其中行从未删除,因此id序列中没有空格。

在删除行(例如空格)的情况下,如果重试此方法直到随机选择现有的主键,此方法仍然可以使用。

参考