【问题标题】:How to get two random records with Django如何使用 Django 获取两条随机记录
【发布时间】:2010-12-16 10:11:12
【问题描述】:

如何使用 Django 获得两个不同的随机记录?我已经看到有关如何获得一个的问题,但我需要获得两个随机记录,它们必须不同。

【问题讨论】:

    标签: python django django-models random sql-order-by


    【解决方案1】:

    其他答案建议的order_by('?')[:2] 解决方案实际上对于具有大量行的表来说是一件非常糟糕的事情。它会产生一个ORDER BY RAND() SQL 查询。例如,下面是 mysql 处理的方式(其他数据库的情况并没有太大的不同)。假设您的表有 10 亿行:

    1. 要完成ORDER BY RAND(),它需要一个RAND() 列进行排序。
    2. 为此,它需要一个新表(现有表没有这样的列)。
    3. 为此,mysql 使用新列创建一个新的临时表,并将现有的 10 亿行数据复制到其中。
    4. 在执行此操作时,它会按照您的要求执行操作,并为每一行运行 rand() 以填充该值。是的,您已经指示 mysql 生成 10 亿个随机数。这需要一段时间。 :)
    5. 几小时/几天后,当它完成后,现在必须对其进行排序。是的,您已指示 mysql 对这十亿行、最坏情况排序的表进行排序(最坏情况,因为排序键是随机的)。
    6. 几天/几周后,完成后,它会忠实地抓取您实际需要的两行并为您返回。不错的工作。 ;)

    注意:只是为了一点额外的肉汁,请注意 mysql 最初会尝试在 RAM 中创建该临时表。当这用尽时,它会暂停一切以将整个内容复制到磁盘,因此您几乎在整个过程中都会遇到额外的 I/O 瓶颈。

    怀疑者应该查看生成的查询以确认它是ORDER BY RAND(),然后在 Google 上搜索“order by rand()”(带引号)。

    更好的解决方案是将一个 非常 昂贵的查询换成三个便宜的查询(限制/偏移而不是 ORDER BY RAND()):

    import random
    last = MyModel.objects.count() - 1
    
    index1 = random.randint(0, last)
    # Here's one simple way to keep even distribution for
    # index2 while still gauranteeing not to match index1.
    index2 = random.randint(0, last - 1)
    if index2 == index1: index2 = last
    
    # This syntax will generate "OFFSET=indexN LIMIT=1" queries
    # so each returns a single record with no extraneous data.
    MyObj1 = MyModel.objects.all()[index1]
    MyObj2 = MyModel.objects.all()[index2]
    

    【讨论】:

    • +1 非常好的解释和一个很好的例子!请记住,在大多数存储引擎(除了 MySQL MyISAM 之外的任何引擎?)上,它必须遍历整个数据集才能获取表的 count,因此这也可能相当昂贵。一般来说,获取随机记录是一件相当昂贵的事情。
    • 是的,我正在尝试获取 1000 条随机记录。这种方法是残酷的。真的站不住脚。
    • 为什么不尝试获取所有mymodel对象并使用len(),比如obj = MyModel.objects.all() index = randint(0,len(obj)-1)
    • @paynestrike 抱歉,如果您是在开玩笑——以防万一:表越大,一次获取所有对象的想法就越糟糕。您并不想实例化十亿个对象,以便随机选择两个对象。
    • @Manganeez,我的意思是 order_by('?') 站不住脚,而不是这里的解决方案。抱歉不清楚,我的错。
    【解决方案2】:

    如果您在 ORM 中指定随机运算符,我很确定它会给您两个不同的随机结果,不是吗?

    MyModel.objects.order_by('?')[:2] # 2 random results.
    

    【讨论】:

    • 是的,这行得通。我在最后添加了 .get() 导致错误。
    • 这可能存在性能问题。详情见Manganeez's answer
    • 同样这种获取可能会返回重复记录
    【解决方案3】:

    为未来的读者。

    获取所有记录的id列表:

    my_ids = MyModel.objects.values_list('id', flat=True)
    my_ids = list(my_ids)
    

    然后从上面所有的id中随机挑选n个id:

    n = 2
    rand_ids = random.sample(my_ids, n)
    

    并获取这些 id 的记录:

    random_records = MyModel.objects.filter(id__in=rand_ids)
    

    【讨论】:

    • 谢谢,这个解决方案对我有用。不过请注意,我必须做my_ids = list(my_ids),否则我会从random.sample 得到TypeError。我还实现了一种方法来处理 my_ids 的记录少于我的样本大小,通过执行 sample_size = min(len(my_ids), 10)
    • 这实际上在数据传输和内存使用方面可能非常昂贵。它返回表中所有 ID 的完整列表,以便它可以选择 2(或 n)。如果您的桌子很大,这可能会让人望而却步。
    • 我认为这不是一条路。当您再次提取记录时,django 默认会使用查询集再次对其进行排序。
    【解决方案4】:

    Object.objects.order_by('?')[:2]

    这将返回两个随机排序的记录。你可以添加

    distinct()
    

    如果您的数据集中存在具有相同值的记录。

    【讨论】:

    • 使用distinct(),您最终可能只有 1 个值。
    【解决方案5】:

    关于从一个序列中采样n个随机值,可以使用随机库,

    random.Random().sample(range(0,last),2) 
    

    将从序列元素中获取 2 个随机样本,从 0 到 last-1

    【讨论】:

      【解决方案6】:

      from django.db import models
      from random import randint
      from django.db.models.aggregates import Count
      
      
      class ProductManager(models.Manager):
          def random(self, count=5):
              index = randint(0, self.aggregate(count=Count('id'))['count'] - count)
              return self.all()[index:index + count]

      您可以获得不同数量的对象。

      【讨论】:

        【解决方案7】:
        class ModelName(models.Model):
        
            # Define model fields etc
        
        
            @classmethod
            def get_random(cls, n=2):
                """Returns a number of random objects. Pass number when calling"""
        
                import random
                n = int(n) # Number of objects to return
                last = cls.objects.count() - 1
                selection = random.sample(range(0, last), n)
                selected_objects = []
                for each in selection:
                    selected_objects.append(cls.objects.all()[each])
                return selected_objects
        

        【讨论】:

          猜你喜欢
          • 2017-10-04
          • 2010-11-01
          • 2017-06-05
          • 2019-07-26
          • 1970-01-01
          • 1970-01-01
          • 2018-06-06
          • 2011-07-17
          相关资源
          最近更新 更多