【问题标题】:Django: Get 10 random instances from a queryset and order them into a new queryset?Django:从查询集中获取 10 个随机实例并将它们排序到新的查询集中?
【发布时间】:2015-09-04 03:17:09
【问题描述】:

我想为我的应用创建一个动态主页,该主页在每次访问主页时包含 10 个不同的页面/用户个人资料。我知道用于随机查询的 django SQL 查询非常慢,所以我试图通过创建一个空列表并创建一个随机数列表然后抓取随机第 n 个元素来编写我自己的方法来执行此(伪)随机示例查询集并将其放入列表中。

import random
profilelist = [] #create an empty list
qindex = ProfilePage.objects.filter(profileisbannedis=False) #queryset for all of possible profiles to be displayed
randlist = random.sample(xrange(qindex.count()), 10) #create a list of 10 numbers between range 0 and the size of the queryset. 
#this method also does not repeat the same randomly generated number which is ideal since I don't want to feature the same profile twice
for i in randlist: 
    tile = qindex[i] #for each random number created, get that element of the queryset
    profilelist.extend(tile) #place each object in the previous queryset into a new list of objects and continue extending the list for each of 10 random numbers

我真的不知道该怎么做,因为我知道我在代码的最后一行收到错误“对象不可迭代”,所以像这样逐个创建一个新的查询集是不合适的方法。我该如何去做/创建从以前过滤的查询集制作的随机查询集?

【问题讨论】:

    标签: python django


    【解决方案1】:

    您可以做的一件事是获取查询集中随机元素的 id 列表(假设“id”是主键),然后对它们进行过滤。类似于下面的代码:

    import random
    valid_profiles_id_list = ProfilePage.objects.filter(profileisbannedis=False).values_list('id', flat=True)
    random_profiles_id_list = random.sample(valid_profiles_id_list, min(len(valid_profiles_id_list), 10))
    query_set = ProfilePage.objects.filter(id__in=random_profiles_id_list)
    

    希望对你有帮助,也请转至django queryset docs

    【讨论】:

    • 这很有帮助,也是我现在使用的方式。感谢您的帮助。我给奥斯汀 A 答案的唯一原因是因为它是在你之前发布的。
    • 我相信你应该改变它,因为奥斯汀的回答是基于一个假设,在几乎所有现实生活中的数据库中都是错误的。
    • 对于 Python 3.8,您应该使用 list(valid_profiles_id_list) 作为 render.sample 函数的第一个参数。
    【解决方案2】:

    经过快速测试,我发现使用 xrangerandom.sample 确实提供了一个列表,因此 xrange 不是您的问题。

    >>> import random
    >>> a = xrange(100)
    >>> rnd = random.sample(a, 10)
    >>> rnd
    [41, 83, 89, 73, 37, 58, 38, 99, 10, 84]
    

    我以前用 django 做过这个。下面是来自应用程序的代码 sn-p。我唯一不同的是在所有对象上使用count() 而不是过滤器。我的下一个建议是确保 django 过滤器的计数符合您的预期。

    # Choose 10 random records to show
    num_entities = Entity.objects.all().count()
    rand_entities = random.sample(range(num_entities), 10)
    sample_entities = Entity.objects.filter(eid__in=rand_entities)
    

    【讨论】:

    • @EazyC- 如果对象的 id 不连续,它将不起作用,即您不能确定如果有 10 个对象,它们的 id 将是 1-10,它们可以是任何取决于对象的创建和删除。
    • @SaurabhGoyal 这是一个很好的警告。如果表只有 CREATE、READ 和 UPDATE,这将正常工作。但是如果可以删除记录(这听起来像是可能的,因为您正在使用配置文件),那么rand_entities 中生成的 id 可能在Entity 中不存在。有几种方法可以解决这个问题,但 Saurabh 的 valid_profile_id_list 就是其中之一。
    • 实际上,即使表只有CREATE、READ和UPDATE,如果你使用事务,它也工作,因为回滚的事务在大多数情况下不会恢复顺序db 引擎(例如 Postgres 和 Oracle)。因此,即使不删除任何行,您也会有空白。
    • @spectras 你能举个例子吗?我不是怀疑你,只是在设想回滚事务的场景时遇到了麻烦。
    • 1) 启动一个事务并向表中插入一行,假设它的 ID 为 42。/2) 回滚事务。 / 3) 在表中插入另一行。该行的 ID 为 43。这是因为序列不会与事务一起回滚。这给 id 42 留下了空白。
    【解决方案3】:

    在你变得过于复杂之前,我建议你测试一下order_by('?') 在你的数据库上是否真的很慢。

    在你的问题中,你说:

    我知道用于随机查询的 django SQL 查询非常慢,所以我正在尝试编写自己的方法......

    这是 Django documentation 所说的:

    注意:order_by('?') 查询可能既昂贵又缓慢,具体取决于您使用的数据库后端。

    所以你应该在这里检查你的数据库是否存在性能问题。

    其他答案提出了两个选择:

    1. 在 0 和 count-1 之间选择随机数,然后根据这些 id 进行过滤。
    2. 选择数据库中所有 id 的列表,然后从列表中随机选择一些,并根据这些 id 进行过滤。

    选项 1 存在边界条件和 ID 编号间隙的问题。 选项 2 进行两个数据库查询,其中一个返回数据库中的所有 ID 号。 这两个选项都可能返回按 id 编号排序的最终选择。

    鉴于所有这些问题和日益增加的复杂性,您至少应该衡量收益以决定是否值得。

    当我在 SQLite3 数据库中选择 1000 条小记录中的 10 条来测量选项 1 与 order_by('?') 的性能时,结果如下:

    Select in database with random order:
    205, 49, 28, 542, 428, 1, 337, 860, 374, 303
    [8.38821005821228, 7.809916019439697, 7.193678855895996, 8.39355993270874, 8.132720947265625]
    Filter by random id numbers:
    135, 357, 406, 476, 552, 580, 662, 663, 670, 889
    [8.62951397895813, 8.145615100860596, 8.251683950424194, 7.629027843475342, 7.384187936782837]
    

    这是我在 PostgreSQL 中尝试相同操作时的结果:

    Select in database with random order:
    117, 337, 160, 500, 468, 178, 845, 542, 735, 525
    [13.016371965408325, 12.65379810333252, 12.106752872467041, 12.485779047012329, 12.837188959121704]
    Filter by random id numbers:
    59, 65, 108, 161, 213, 246, 301, 813, 854, 969
    [18.311591863632202, 20.5823872089386, 13.955725193023682, 13.034253120422363, 13.079485177993774]
    

    如果有显着差异,order_by('?') 看起来更好。它在您的数据库中看起来如何?如果您决定使用选项 1,请更改边界以匹配您的身份证号码。如果您决定选择选项 2,其他答案看起来都不错。

    这是我用来测试 SQLite3 版本的代码。您可以将其保存到文件并按原样运行:

    # Tested with Django 1.9.2
    import sys
    import timeit
    from random import sample
    
    import django
    from django.apps import apps
    from django.apps.config import AppConfig
    from django.conf import settings
    from django.db import connections, models, DEFAULT_DB_ALIAS
    from django.db.models.base import ModelBase
    
    NAME = 'udjango'
    SELECT_COUNT = 10
    base_query = None
    
    
    def select_in_database():
        chosen = base_query.order_by('?')[:SELECT_COUNT]
        return list(chosen)
    
    
    def select_by_random_id():
        db_size = base_query.count()
        random_ids = sample(xrange(db_size), SELECT_COUNT)
        chosen = base_query.filter(id__in=random_ids)
        return list(chosen)
    
    
    def main():
        global base_query
        setup()
    
        class Person(models.Model):
            first_name = models.CharField(max_length=30)
            last_name = models.CharField(max_length=30)
    
        syncdb(Person)
    
        for i in range(1000):
            Person.objects.create(first_name=str(i), last_name=str(i))
    
        base_query = Person.objects.all()
    
        print('Select in database with random order:')
        print(', '.join(person.first_name for person in select_in_database()))
        print(timeit.repeat('select_in_database()',
                            'from __main__ import select_in_database',
                            repeat=5,
                            number=10000))
        print('Filter by random id numbers:')
        print(', '.join(person.first_name for person in select_by_random_id()))
        print(timeit.repeat('select_by_random_id()',
                            'from __main__ import select_by_random_id',
                            repeat=5,
                            number=10000))
    
    
    def setup():
        DB_FILE = NAME + '.db'
        with open(DB_FILE, 'w'):
            pass  # wipe the database
        settings.configure(
            DEBUG=True,
            DATABASES={
                DEFAULT_DB_ALIAS: {
                    'ENGINE': 'django.db.backends.sqlite3',
                    'NAME': DB_FILE}},
            LOGGING={'version': 1,
                     'disable_existing_loggers': False,
                     'formatters': {
                        'debug': {
                            'format': '%(asctime)s[%(levelname)s]'
                                      '%(name)s.%(funcName)s(): %(message)s',
                            'datefmt': '%Y-%m-%d %H:%M:%S'}},
                     'handlers': {
                        'console': {
                            'level': 'DEBUG',
                            'class': 'logging.StreamHandler',
                            'formatter': 'debug'}},
                     'root': {
                        'handlers': ['console'],
                        'level': 'WARN'},
                     'loggers': {
                        "django.db": {"level": "WARN"}}})
        app_config = AppConfig(NAME, sys.modules['__main__'])
        apps.populate([app_config])
        django.setup()
        original_new_func = ModelBase.__new__
    
        @staticmethod
        def patched_new(cls, name, bases, attrs):
            if 'Meta' not in attrs:
                class Meta:
                    app_label = NAME
                attrs['Meta'] = Meta
            return original_new_func(cls, name, bases, attrs)
        ModelBase.__new__ = patched_new
    
    
    def syncdb(model):
        """ Standard syncdb expects models to be in reliable locations.
    
        Based on https://github.com/django/django/blob/1.9.3
        /django/core/management/commands/migrate.py#L285
        """
        connection = connections[DEFAULT_DB_ALIAS]
        with connection.schema_editor() as editor:
            editor.create_model(model)
    
    
    main()
    

    【讨论】:

    • 很棒的解决方案!
    【解决方案4】:

    首先,我认为您应该将配置文件列表的“扩展”替换为“附加”,qindex[i] 不可迭代。

    第二,我觉得最简单的方法是:

    q_ids = qindex.values_list('id', flat=True)
    r_ids = random.sample(q_ids, 10)
    return qindex.filter(id__in=r_ids)
    

    试一试,:)

    【讨论】:

      【解决方案5】:

      我更喜欢在每次页面加载时对整个查询集进行洗牌,并对模板中的前十个对象进行切片。 在视图中:

      ids = [i.id for i in Model.objects.filter(some_field=some_variable)]
      random.shuffle(ids)
      shuffled = [Model.objects.get(id=i) for i in ids]
      

      在模板中:

      {% for i in shuffled|slice:'10' %}
      
      

      【讨论】:

        猜你喜欢
        • 2020-06-03
        • 2021-12-10
        • 2018-12-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-27
        相关资源
        最近更新 更多