如何使用Django的ORM拉随机logging?
我有一个模型,代表我在网站上展示的绘画。 在主网页上,我想展示其中的一些:最新的,大多数时间没有访问的,最stream行的和随机的。
我正在使用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)
这看起来不像我想要的东西 – 这完全是数据库抽象的一部分,应该在模型中。 此外,在这里我需要照顾删除logging(然后所有logging的数量将不会涵盖所有可能的关键值),可能还有很多其他的东西。
任何其他选项我怎么能做到这一点,最好是在模型抽象内部?
使用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中有logging 。
如果使用MySQL(不知道其他数据库),使用order_by('?')[:N]的解决scheme即使对于中等大小的表也非常慢。
order_by('?')[:N]
将被转换为SELECT ... FROM ... WHERE ... ORDER BY RAND() LIMIT N
查询。
这意味着对于表中的每一行都会执行RAND()函数,然后整个表将按照该函数的值进行sorting,然后返回前N个logging。 如果你的桌子很小,这很好。 但在大多数情况下,这是一个非常缓慢的查询。
我写了一个简单的函数,即使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('?')更快。
你可以在你的模型上创build一个pipe理器来做这种事情。 为了首先了解pipe理者是什么, Painting.objects
方法是一个包含all()
, filter()
, get()
等的pipe理器。创build自己的pipe理器允许您预先筛选结果并使用所有这些方法,以及你自己的习惯方法,在结果上工作。
编辑 :我修改我的代码,以反映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]
最后,你可以在你的模型上有许多经理,所以随意创build一个LeastViewsManager()
或者MostPopularManager()
。
其他答案可能很慢(使用order_by('?')
)或使用多个SQL查询。 这里有一个没有sorting和只有一个查询(假设Postgres)的示例解决scheme:
Model.objects.raw(''' select * from {0} limit 1 offset floor(random() * (select count(*) from {0})) '''.format(Model._meta.db_table))[0]
请注意,如果表格为空,则会引发索引错误。 写自己的模型不可知的帮助函数来检查。
这是高度推荐从关系数据库中获取一个随机行
因为使用django orm来做这样的事情,如果你有大数据表会使你的db服务器生气:
解决scheme是提供一个模型pipe理器,并手工编写SQL查询;)
更新 :
另一种解决scheme,可以在任何数据库后端工作,甚至不需要编写自定义模型pipe理器的非rel相关。 在Django中从Queryset中获取Random对象
只是一个简单的想法,我怎么做:
def _get_random_service(self, professional): services = Service.objects.filter(professional=professional) i = randint(0, services.count()-1) return services[i]
一个更简单的方法就是简单地过滤到感兴趣的logging集,然后使用random.sample
来select尽可能多的数据:
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
请注意,您应该有一些代码来validationmy_queryset
不是空的; random.sample
返回ValueError: sample larger than population
如果第一个参数包含的元素太less,则ValueError: sample larger than population
。
这是一个简单的解决scheme:
from random import randint count = Model.objects.count() random_object = Model.objects.all()[randint(0, count - 1)] #single random object
只需要注意一个(相当常见的)特殊情况,如果表中没有删除索引的自动增量列,那么执行随机select的最佳方法是查询如下:
SELECT * FROM table WHERE id = RAND() LIMIT 1
假定这样一个名为id的列用于表格。 在Django中,您可以通过以下方式来完成此操作:
Painting.objects.raw('SELECT * FROM appname_painting WHERE id = RAND() LIMIT 1')
在其中您必须用应用程序名称replaceappname。
一般来说,使用id列,order_by('?')可以更快地完成:
Paiting.objects.raw( 'SELECT * FROM auth_user WHERE id>=RAND() * (SELECT MAX(id) FROM auth_user) LIMIT %d' % needed_count)
您可能希望使用与用于对任何迭代器进行采样的相同方法 ,特别是如果您计划对多个项目进行采样以创build一个采样集合 。 @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
简单地这样做有什么不妥:
import random records = Model.objects.all() random_record = random.choice(records)