【问题标题】:Rails select random recordRails 选择随机记录
【发布时间】:2011-04-08 03:36:39
【问题描述】:

我不知道我是不是在这里看错了地方还是什么,但是活动记录有检索随机对象的方法吗?

类似的东西?

@user = User.random

或者......好吧,既然这种方法不存在,那么是否有一些惊人的“Rails 方式”可以做到这一点,我似乎总是很冗长。我也在用mysql。

【问题讨论】:

    标签: mysql ruby-on-rails ruby


    【解决方案1】:

    我见过的大多数这样做的示例最终都会计算表中的行数,然后生成一个随机数来选择一个。这是因为诸如RAND() 之类的替代方案效率低下,因为它们实际上获取每一行并为它们分配一个随机数,或者我已经阅读过(并且我认为是特定于数据库的)。

    您可以添加一个类似我找到的方法 here

    module ActiveRecord
      class Base
        def self.random
          if (c = count) != 0
            find(:first, :offset =>rand(c))
          end
        end
      end
    end
    

    这将使您使用的任何模型都有一个名为 random 的方法,该方法以我上面描述的方式工作:在表中的行数内生成一个随机数,然后获取与该随机数关联的行数字。所以基本上,你只做一次你可能更喜欢的 fetch :)

    你也可以看看this rails plugin

    【讨论】:

    • 这很好。我喜欢它,因为它也不是特定于 ORM 的。干得好,谢谢!
    • 在 Rails 4.1 中,我不得不使用:offset(rand(c)).first
    【解决方案2】:

    我们发现,对于一个大表,MySql 上的偏移量非常运行得很慢。而不是像这样使用偏移量:

    model.find(:first, :offset =>rand(c))
    

    ...我们发现以下技术的运行速度提高了 10 倍以上(固定为 1):

    max_id = Model.maximum("id")
    min_id = Model.minimum("id")
    id_range = max_id - min_id + 1
    random_id = min_id + rand(id_range).to_i
    Model.find(:first, :conditions => "id >= #{random_id}", :limit => 1, :order => "id")
    

    【讨论】:

    • 最后一行有点太冗长了。这做同样的事情(在 Rails 3 中):Model.where("id >= #{random_id}").first
    • 如果任何记录已被删除,则方法不会生成均匀分布的结果,这通常是人们所期望的。想象一下存在 id 1-10 的情况,除了 id 5。在这种情况下,当随机数生成器产生 5 或 6(20% 的时间)时,将返回 id 为 6 的模型,而其他每个现有的 id 仅在 10% 的时间内被选中。也许不是破坏交易,但需要注意的事情。
    【解决方案3】:

    尝试使用Array的sample方法:

    @user = User.all.sample(1)
    

    【讨论】:

    • 我喜欢你的回答,这是一些真正的红宝石风格
    • 您应该删除 (1),因为这会导致单例数组。使用 User.all.sample 只会产生一个用户
    • 如果您有大量数据,您绝对不应该这样做 - 它会尝试将所有用户加载到内存中。如果您使用的是 Rails 4 和 Postgres,请使用 User.order("RANDOM()").limit(10)(来自 stackoverflow.com/a/17373279/1298553)。
    【解决方案4】:

    在 Rails 4 中我会扩展 ActiveRecord::Relation:

    class ActiveRecord::Relation
      def random
        offset(rand(count))
      end
    end
    

    这样你就可以使用作用域了:

    SomeModel.all.random.first # Return one random record
    SomeModel.some_scope.another_scope.random.first
    

    【讨论】:

      【解决方案5】:

      我会使用命名范围。只需将其放入您的用户模型中即可。

      named_scope :random, :order=>'RAND()', :limit=>1
      

      不过,每个数据库中的随机函数并不相同。 SQLite 和其他人使用RANDOM(),但你需要使用RAND() 来用于 MySQL。

      如果您希望能够抓取多个随机行,您可以试试这个。

      named_scope :random, lambda { |*args| { :order=>'RAND()', :limit=>args[0] || 1 } }
      

      如果您调用 User.random,它将默认为 1,但如果您需要多个,也可以调用 User.random(3)

      【讨论】:

      • 我听说 RAND() 真的很慢,因为它首先获取每条记录,然后以某种方式选择一条记录,但我可能错了。
      • @Blaenk:在 MySQL 上它非常慢。不过我不知道具体的实现。
      【解决方案6】:

      如果您需要随机记录但仅在某些条件下,您可以使用此代码中的“random_where”:

      module ActiveRecord
        class Base
          def self.random
            if (c = count) != 0
              find(:first, :offset =>rand(c))
            end
          end
      
          def self.random_where(*params)
            if (c = where(*params).count) != 0
              where(*params).find(:first, :offset =>rand(c))
            end
          end
      
        end
      end
      

      例如:

      @user = User.random_where("active = 1")
      

      此功能对于根据一些附加条件显示随机产品非常有用

      【讨论】:

      • Rails 3 和主动关系链处理了这种情况。您可以只使用 User.random.where('active = 1') 而无需额外的全局方法。
      • "find" 方法不返回 Relation 对象,它返回模型对象或数组:),但是!你可以这样做User.where('active = 1').random,这会很好用......我不知道为什么我错过了:)
      • 你是对的。好眼力。我想了一件事,又输入了另一件事。 :-)
      【解决方案7】:

      强烈推荐这个gem用于随机记录,它是专门为具有大量数据行的表设计的:

      https://github.com/haopingfan/quick_random_records

      简单用法:

      @user = User.random_records(1).take


      所有其他答案在大型数据库中表现不佳,除了这个 gem:

      1. quick_random_records 总共只花费了4.6ms

      1. 接受的答案User.order('RAND()').limit(10) 花费733.0ms

      1. offset 方法的总成本为 245.4ms

      1. User.all.sample(10) 方法成本573.4ms

      注意:我的表只有 120,000 个用户。您拥有的记录越多,性能差异就越大。


      更新:

      在有 550,000 行的表上执行

      1. Model.where(id: Model.pluck(:id).sample(10)) 费用 1384.0ms

      1. gem: quick_random_records 仅花费 6.4ms 全部

      【讨论】:

        【解决方案8】:

        这是从数据库中获取随机记录的最佳解决方案。 RoR 提供一切易用性。

        要从数据库中获取随机记录,请使用sample,下面是示例说明。

        基于 Marc-Andre Lafortune 的 github.com/marcandre/backports/ 的数组的反向移植#sample 从数组中返回一个随机元素或 n 个随机元素。如果数组为空且 n 为 nil,则返回 nil。如果传递了 n 并且其值小于 0,则会引发 ArgumentError 异常。如果 n 的值等于或大于 0,则返回 []。

        [1,2,3,4,5,6].sample     # => 4     
        [1,2,3,4,5,6].sample(3)  # => [2, 4, 5]     
        [1,2,3,4,5,6].sample(-3) # => ArgumentError: negative array size     
        [].sample     # => nil     
        [].sample(3)  # => []     
        

        您可以根据您的要求使用条件,如下例所示。

        User.where(active: true).sample(5)

        它会从用户表中随机返回 5 个活跃用户

        如需更多帮助,请访问:http://apidock.com/rails/Array/sample

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-08-31
          • 2011-09-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多