ChatGPT解决这个技术问题 Extra ChatGPT

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

我有一个模型代表我在我的网站上展示的画作。在主网页上,我想展示其中的一些:最新的、大部分时间未访问的、最受欢迎的和随机的。

我正在使用 Django 1.0.2。

虽然使用 django 模型很容易提取其中的前 3 个,但最后一个(随机)给我带来了一些麻烦。在我看来,我可以将其编码为如下所示:

number_of_records = models.Painting.objects.count()
random_index = int(random.random()*number_of_records)+1
random_paint = models.Painting.get(pk = random_index)

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

任何其他选项我可以如何做到这一点,最好是在模型抽象中以某种方式?

在我看来,如何显示事物以及显示哪些事物是“视图”级别或业务逻辑的一部分,应该进入 MVC 的“控制器”级别。
在 Django 中,控制器是视图。 docs.djangoproject.com/en/dev/faq/general/…
应该有一个内置函数 - 一个不使用 order_by('?')

C
Community

只需使用:

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

它记录在 QuerySet API 中。


请注意,这种方法可能非常慢,如文档所述:)
“可能既昂贵又缓慢,具体取决于您使用的数据库后端。” - 有关于不同数据库后端的经验吗? (sqlite/mysql/postgres)?
我还没有测试过,所以这纯粹是猜测:为什么它比检索所有项目并在 Python 中执行随机化要慢?
我读到它在 mysql 中很慢,因为 mysql 的随机排序非常低效。
为什么不只是 random.choice(Model.objects.all())
N
Nathan Tuggy

使用 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]

model.objects.aggregate(count=Count('id'))['count']model.objects.all().count() 有什么好处
虽然比公认的答案好得多,但请注意,这种方法会进行两个 SQL 查询。如果计数在两者之间发生变化,则可能会出现越界错误。
也许注释随机(自我)应该用“@transaction.atomic”注释以避免改变计数问题? docs.djangoproject.com/ja/1.9/topics/db/transactions
这是一个错误的解决方案。如果您的 id 不是从 0 开始,它将不起作用。并且当 id 不连续时也是如此。比如说,第一条记录从 500 开始,最后一条记录是 599(假设连续)。然后计数将是 54950。肯定 list[54950] 不存在,因为您的查询长度为 100。它会抛出索引超出范围异常。我不知道为什么这么多人赞成这个,这被标记为接受的答案。
@sajid:为什么,确切地说,你在问我吗?很容易看到我对这个问题的贡献的总和:编辑链接以指向腐烂后的档案。我什至没有对任何答案投票。但我确实觉得这个答案和你声称要好得多的答案都使用 .all()[randint(0, count - 1)] 很有趣。也许您应该专注于找出答案的哪一部分是错误的或薄弱的,而不是为我们重新定义“一个错误”,并对愚蠢的选民大喊大叫。 (也许是它没有使用 .objects?)
M
Mikhail Korobov

如果您使用 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('?') 快。


此外,可悲的是,它远非随机。如果您有一个 id 为 1 的记录和另一个 id 为 100 的记录,那么它将在 99% 的情况下返回第二个记录。
M
Maulik Patel

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

from random import randint

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

S
Soviut

您可以在模型上创建一个 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()


仅当您的 pk 是连续的时,使用 get() 才有效,即您永远不会删除任何项目。否则你很可能会尝试得到一个不存在的pk。使用 .all()[random_index] 不会遇到这个问题,效率也不会降低。
我明白这就是为什么我的示例只是将问题的代码与经理一起复制的原因。仍然由 OP 来完成他的边界检查。
而不是使用 .get(id=random_index) 使用 .filter(id__gte=random_index)[0:1] 会更好吗?首先,它有助于解决非连续pks的问题。其次,get_query_set 应该返回...一个 QuerySet。在你的例子中,它没有。
我不会仅仅为了容纳一种方法而创建一个新的经理。我会将“get_random”添加到默认管理器中,这样您就不必在每次需要随机图像时都通过 all()[0] 循环。此外,如果作者是用户模型的外键,您可以说 user.painting_set.get_random()。
当我想要一揽子行动时,我通常会创建一个新经理,例如获取随机记录列表。如果我正在使用我已经拥有的记录执行更具体的任务,我会在默认管理器上创建一个方法。
j
jerinisready

其他答案可能很慢(使用 order_by('?'))或使用多个 SQL 查询。这是一个没有排序的示例解决方案,只有一个查询(假设 Postgres):

random_instance_or_none = Model.objects.raw('''
    select * from {0} limit 1
    offset floor(random() * (select count(*) from {0}))
'''.format(Model._meta.db_table)).first()

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


一个很好的概念证明,但这也是数据库内部的两个查询,您保存的是到数据库的一次往返。您必须多次执行此操作才能使编写和维护原始查询值得。如果您想防止空表,您不妨提前运行 count() 并省去原始查询。
V
Valter Silva

只是一个简单的想法,我是如何做到的:

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

j
jerinisready

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

所以基本上我们在 DB 有数据,我们想使用 python 的 rand 函数,然后从 DB 中调出所需的全部数据。

基本上使用 2 个查询比在 DB CPU 中随机选择(在 DB 中计算)或加载整个数据(大量网络利用率)要便宜得多。解释的解决方案必须具有可扩展性,试图在此处进行计划将不适用于生产环境,尤其是带有过滤器、软/硬删除,甚至带有 is_public 标志的生产环境。因为我们生成的随机 id 可能会从数据库中删除,或者会在过滤器中被删除。假设 max_id(records) == count(records) 是一种不好的做法。

(Ofcouce,如果您不删除与查询使用相当的数据百分比,或者如果您不想使用任何过滤器,并且如果您有信心,您可以使用随机 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

当您有数百万行时,即使您只选择 ID,这也会杀死内存。
p
pjmnoble

嗨,我需要从查询集中选择一条随机记录,我还需要报告其长度(即网页生成了描述的项目并留下了所述记录)

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

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

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

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


D
Daniel Himmelstein

不删除的自动递增主键的方法

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

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

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

在已删除行的情况下,如果在随机选择现有主键之前重试此方法,则此方法仍然有效。

参考

https://stackoverflow.com/a/10836811/4651668

https://stackoverflow.com/a/2118712/4651668

https://stackoverflow.com/a/39751708/4651668

https://github.com/greenelab/hetmech-backend/pull/48


A
Amir Ali Akbari

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

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)

A
Alireza Savand

这是强烈推荐 Getting a random row from a relational database

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

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

更新:

另一种解决方案适用于任何数据库后端,即使是非 rel 后端也无需编写自定义 ModelManagerGetting Random objects from a Queryset in Django


C
Community

您可能希望使用用于对任何迭代器进行采样的 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

Matijn 和 DxinX 的解决方案是针对不提供随机访问的数据集。对于这样做的数据集(SQL 使用 OFFSET),这是不必要的低效。
@EndreBoth 确实如此。我只是喜欢不管数据源如何都使用相同方法的编码“效率”。有时,数据采样效率不会显着影响受其他流程限制的管道的性能(无论您实际对数据做什么,例如 ML 训练)。
e
eykanal

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


这会导致整个查询集被检索吗?
@perrohunter 它甚至不适用于 Queryset (至少对于 Python 3.7 和 Django 2.1);您必须先将其转换为列表,这显然会检索整个查询集。
@EndreBoth - 这是在 2016 年写的,当时都不存在。
这就是我添加版本信息的原因。但如果它在 2016 年有效,它是通过将整个查询集拉到一个列表中来实现的,对吧?
@EndreBoth 正确。
L
LagRange

我得到了非常简单的解决方案,制作自定义管理器:

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()

从随机进口选择
如果你想要速度,请不要使用这种方法。这个解决方案非常慢。我已经检查过了。它比 order_by('?').first() 慢 60 多倍。
@Alex78191 不,“?”也很糟糕,但我的方法非常慢。我使用了最佳答案解决方案。