ActiveRecord中的随机记录

时间:2010-05-02 02:11:53

标签: ruby-on-rails random rails-activerecord

我需要通过ActiveRecord从表中获取随机记录。我按照Jamis Buck from 2006中的示例进行了操作。

但是,我也通过Google搜索遇到了另一种方式(由于新的用户限制,无法使用链接进行归因):

 rand_id = rand(Model.count)
 rand_record = Model.first(:conditions => ["id >= ?", rand_id])

我很好奇其他人是如何做到的,或者有人知道哪种方式更有效率。

26 个答案:

答案 0 :(得分:173)

Rails 4 5 中,使用 Postgresql SQLite ,使用RANDOM():< / p>

Model.order('RANDOM()').first

据推测,对于带有RAND()

MySQL ,这同样适用
Model.order('RAND()').first

is about 2.5 times中的方法 accepted answer更快

警告:对于包含数百万条记录的大型数据集,这种情况很慢,因此您可能需要添加limit子句。

答案 1 :(得分:128)

如果没有至少两次查询,我还没有找到理想的方法。

以下使用随机生成的数字(最多为当前记录数)作为偏移量

offset = rand(Model.count)

# Rails 4
rand_record = Model.offset(offset).first

# Rails 3
rand_record = Model.first(:offset => offset)

老实说,我刚刚使用ORDER BY RAND()或RANDOM()(取决于数据库)。如果您没有性能问题,这不是性能问题。

答案 2 :(得分:73)

一旦删除记录,您的示例代码将开始表现不正确(它将不公平地支持具有较低ID的项目)

您最好在数据库中使用随机方法。这些因您使用的数据库而异,但是:order =&gt; “RAND()”适用于mysql和:order =&gt; “RANDOM()”适用于postgres

Model.first(:order => "RANDOM()") # postgres example

答案 3 :(得分:28)

在具有+ 500万条记录的产品表上对MySQL 5.1.49,Ruby 1.9.2p180上的这两种方法进行基准测试:

def random1
  rand_id = rand(Product.count)
  rand_record = Product.first(:conditions => [ "id >= ?", rand_id])
end

def random2
  if (c = Product.count) != 0
    Product.find(:first, :offset =>rand(c))
  end
end

n = 10
Benchmark.bm(7) do |x|
  x.report("next id:") { n.times {|i| random1 } }
  x.report("offset:")  { n.times {|i| random2 } }
end


             user     system      total        real
next id:  0.040000   0.000000   0.040000 (  0.225149)
offset :  0.020000   0.000000   0.020000 ( 35.234383)

MySQL中的偏移似乎要慢得多。

修改的 我也试过

Product.first(:order => "RAND()")

但我必须在约60秒后杀死它。 MySQL是“复制到磁盘上的tmp表”。那不行。

答案 4 :(得分:18)

没有那么难。

ids = Model.pluck(:id)
random_model = Model.find(ids.sample)

pluck返回表中所有id的数组。数组上的sample方法从数组中返回一个随机id。

这应该表现良好,具有相同的选择概率和支持已删除行的表。你甚至可以将它与约束混合在一起。

User.where(favorite_day: "Friday").pluck(:id)

从而挑选一个喜欢星期五而不是任何用户的随机用户。

答案 5 :(得分:13)

我做了一个rails 3 gem来处理这个问题:

https://github.com/spilliton/randumb

它允许你做这样的事情:

Model.where(:column => "value").random(10)

答案 6 :(得分:10)

不建议您使用此解决方案,但如果由于某种原因您确实想要在只进行一次数据库查询时随机选择记录,则可以使用sample Ruby Array class中的方法,允许您从数组中选择随机项。

Model.all.sample

此方法仅需要数据库查询,但它比需要两个数据库查询的Model.offset(rand(Model.count)).first等替代方法要慢得多,但后者仍然是首选。

答案 7 :(得分:8)

我经常在控制台中使用它,我在初始化程序中扩展ActiveRecord - Rails 4示例:

class ActiveRecord::Base
  def self.random
    self.limit(1).offset(rand(self.count)).first
  end
end

然后我可以致电Foo.random带回随机记录。

答案 8 :(得分:5)

Postgres中的一个查询:

User.order('RANDOM()').limit(3).to_sql # Postgres example
=> "SELECT "users".* FROM "users" ORDER BY RANDOM() LIMIT 3"

使用偏移量,两个查询:

offset = rand(User.count) # returns an integer between 0 and (User.count - 1)
Model.offset(offset).limit(1)

答案 9 :(得分:5)

阅读所有这些内容并没有让我对使用Rails 5和MySQL / Maria 5.5在特定情况下最适合哪些内容充满信心。所以我在约65000条记录中测试了一些答案,并且有两个结果:

  1. 带有limit的兰德()明显胜出。
  2. 请勿使用pluck + sample
  3. def random1
      Model.find(rand((Model.last.id + 1)))
    end
    
    def random2
      Model.order("RAND()").limit(1)
    end
    
    def random3
      Model.pluck(:id).sample
    end
    
    n = 100
    Benchmark.bm(7) do |x|
      x.report("find:")    { n.times {|i| random1 } }
      x.report("order:")   { n.times {|i| random2 } }
      x.report("pluck:")   { n.times {|i| random3 } }
    end
    
                  user     system      total        real
    find:     0.090000   0.000000   0.090000 (  0.127585)
    order:    0.000000   0.000000   0.000000 (  0.002095)
    pluck:    6.150000   0.000000   6.150000 (  8.292074)
    

    这个答案综合,验证和更新Mohamed's answer,以及Nami WANG对此的评论以及Florian Pilz对接受答案的评论 - 请向他们发送投票!

答案 10 :(得分:3)

你可以使用Array方法sample,方法sample从数组中返回一个随机对象,为了使用它你只需要执行一个简单的{{1返回集合的查询,例如:

ActiveRecord

会返回这样的内容:

User.all.sample

答案 11 :(得分:2)

如果您需要选择指定范围内的某些随机结果

scope :male_names, -> { where(sex: 'm') }
number_of_results = 10

rand = Names.male_names.pluck(:id).sample(number_of_results)
Names.where(id: rand)

答案 12 :(得分:1)

Rails 4.2和Oracle

对于oracle,您可以在模型上设置范围,如下所示:

scope :random_order, -> {order('DBMS_RANDOM.RANDOM')}

scope :random_order, -> {order('DBMS_RANDOM.VALUE')}

然后对于样本调用它是这样的:

Model.random_order.take(10)

Model.random_order.limit(5)

当然你也可以在没有像这样的范围的情况下下订单:

Model.all.order('DBMS_RANDOM.RANDOM') # or DBMS_RANDOM.VALUE respectively

答案 13 :(得分:1)

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

https://github.com/haopingfan/quick_random_records

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

  1. quick_random_records仅花费4.6ms
  2. enter image description here

    1. User.order('RAND()').limit(10)费用733.0ms
    2. enter image description here

      1. 接受的答案offset接近费用245.4ms
      2. enter image description here

        1. User.all.sample(10)进近费用573.4ms
        2. enter image description here

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

答案 14 :(得分:1)

在看到这么多答案后,我决定在PostgreSQL(9.6.3)数据库上对它们进行基准测试。我使用一个较小的100,000表并摆脱了Model.order(“RANDOM()”)。首先,它已经慢了两个数量级。

使用一个包含2,500,000个条目和10列的表格,获胜者是采摘方式,比跑步者快8倍(偏移。我只在本地服务器上运行这个数字,因此这个数字可能会膨胀,但是它足够大那个采用方法就是我最终会使用的。同样值得注意的是,这可能会导致问题,因为你每次都会获得超过1个结果,因为每个结果都是唯一的,而不是随机的。

Pluck赢得了我25,000,000行表上的100次运行 编辑:实际上这次包括循环中的插入,如果我把它拿出来它运行速度与id上的简单迭代一样快。然而;它确实占用了相当多的RAM。

RandomModel                 user     system      total        real
Model.find_by(id: i)       0.050000   0.010000   0.060000 (  0.059878)
Model.offset(rand(offset)) 0.030000   0.000000   0.030000 ( 55.282410)
Model.find(ids.sample)     6.450000   0.050000   6.500000 (  7.902458)

以下是在我的100,000行表上运行2000次的数据以排除随机

RandomModel       user     system      total        real
find_by:iterate  0.010000   0.000000   0.010000 (  0.006973)
offset           0.000000   0.000000   0.000000 (  0.132614)
"RANDOM()"       0.000000   0.000000   0.000000 ( 24.645371)
pluck            0.110000   0.020000   0.130000 (  0.175932)

答案 15 :(得分:1)

如果你正在使用PostgreSQL 9.5+,你可以利用TABLESAMPLE来选择随机记录。

两种默认的抽样方法(SYSTEMBERNOULLI)要求您指定要返回的行数占表中总行数的百分比。

-- Fetch 10% of the rows in the customers table.
SELECT * FROM customers TABLESAMPLE BERNOULLI(10);

这需要知道表中的记录数量以选择适当的百分比,这可能不容易快速找到。幸运的是,tsm_system_rows module允许您指定要直接返回的行数。

CREATE EXTENSION tsm_system_rows;

-- Fetch a single row from the customers table.
SELECT * FROM customers TABLESAMPLE SYSTEM_ROWS(1);

要在ActiveRecord中使用此功能,请首先在迁移中启用扩展程序:

class EnableTsmSystemRowsExtension < ActiveRecord::Migration[5.0]
  def change
    enable_extension "tsm_system_rows"
  end
end

然后修改查询的from子句:

customer = Customer.from("customers TABLESAMPLE SYSTEM_ROWS(1)").first

我不知道SYSTEM_ROWS抽样方法是完全随机的还是只是从随机页面返回第一行。

大部分信息来自2ndQuadrant blog post written by Gulcin Yildirim

答案 16 :(得分:1)

对于MySQL数据库,请尝试:Model.order(“RAND()”)。first

答案 17 :(得分:1)

从列表中随机选择项目的Ruby方法是sample。想要为ActiveRecord创建一个高效的sample,并根据以前的答案,我使用了:

module ActiveRecord
  class Base
    def self.sample
      offset(rand(size)).first
    end
  end
end

我将其放在lib/ext/sample.rb中,然后将其加载到config/initializers/monkey_patches.rb

Dir[Rails.root.join('lib/ext/*.rb')].each { |file| require file }

如果模型的大小已经被缓存,那么这将是一个查询,否则将是两个。

答案 18 :(得分:0)

该怎么做:

rand_record = Model.find(Model.pluck(:id).sample)

对我来说很清楚

答案 19 :(得分:0)

我在我的应用程序中使用rails 4.2.8 of Benchmark尝试了Sam的示例(我将1..Category.count随机放入,因为如果随机取0,则会产生错误(ActiveRecord: :RecordNotFound:找不到带有&#39; id&#39; = 0))的分类,而我的是:

 def random1
2.4.1 :071?>   Category.find(rand(1..Category.count))
2.4.1 :072?>   end
 => :random1
2.4.1 :073 > def random2
2.4.1 :074?>    Category.offset(rand(1..Category.count))
2.4.1 :075?>   end
 => :random2
2.4.1 :076 > def random3
2.4.1 :077?>   Category.offset(rand(1..Category.count)).limit(rand(1..3))
2.4.1 :078?>   end
 => :random3
2.4.1 :079 > def random4
2.4.1 :080?>    Category.pluck(rand(1..Category.count))
2.4.1 :081?>
2.4.1 :082 >     end
 => :random4
2.4.1 :083 > n = 100
 => 100
2.4.1 :084 > Benchmark.bm(7) do |x|
2.4.1 :085 >     x.report("find") { n.times {|i| random1 } }
2.4.1 :086?>   x.report("offset") { n.times {|i| random2 } }
2.4.1 :087?>   x.report("offset_limit") { n.times {|i| random3 } }
2.4.1 :088?>   x.report("pluck") { n.times {|i| random4 } }
2.4.1 :089?>   end

                  user      system      total     real
find            0.070000   0.010000   0.080000 (0.118553)
offset          0.040000   0.010000   0.050000 (0.059276)
offset_limit    0.050000   0.000000   0.050000 (0.060849)
pluck           0.070000   0.020000   0.090000 (0.099065)

答案 20 :(得分:0)

.order('RANDOM()').limit(limit)看起来很整洁但对于大型表来说很慢,因为即使limit为1(在数据库内部但在Rails中没有),它也需要获取和排序所有行。我不确定MySQL,但这种情况发生在Postgres。 herehere中的更多解释。

大型表格的一个解决方案是.from("products TABLESAMPLE SYSTEM(0.5)"),其中0.5表示0.5%。但是,如果您有WHERE条件过滤掉很多行,我发现此解决方案仍然很慢。我想这是因为TABLESAMPLE SYSTEM(0.5)在适用WHERE条件之前获取所有行。

大表的另一种解决方案(但不是非常随机)是:

products_scope.limit(sample_size).sample(limit)

其中sample_size可以是100(但不要太大,否则会很慢并消耗大量内存),而limit可以是1。请注意,尽管速度很快但并不是随机的,但它仅在sample_size个记录中随机。

PS:以上答案的基准测试结果不可靠(至少在Postgres中),因为第二次运行的某些数据库查询可能比第一次运行快得多,这要归功于数据库缓存。不幸的是,没有简单的方法来禁用Postgres中的缓存以使这些基准可靠。

答案 21 :(得分:0)

我是RoR的新手,但我让它为我工作:

 def random
    @cards = Card.all.sort_by { rand }
 end

它来自:

How to randomly sort (scramble) an array in Ruby?

答案 22 :(得分:0)

一个很老的问题,但有:

rand_record = Model.all.shuffle

您获得了一个记录数组,按随机顺序排序。 不需要宝石或脚本。

如果您想要一个记录:

rand_record = Model.all.shuffle.first

答案 23 :(得分:0)

除了使用getComputer()之外,您还可以将其放入范围内:

       @NonCPS
       def shallTrigger() {
              for (aSlave in hudson.model.Hudson.instance.slaves) {
                    def thisSlave = aSlave.name
                    echo 'Name: ' + thisSlave + ' is being checked.'
                    if ( aSlave.getComputer().isOffline().toString() == 'true') {
                        slaveState = 'OFFLINE'
                        echo 'Name: ' + thisSlave + ' is ' + slaveState + ' !'
                        emailext (
                          mimeType: 'text/html',
                          body: "${env.JOB_NAME} found an OFFLINE node: ${name}  ",
                          subject: "Jenkins ERROR: Build Node ${name} is OFFLINE " ,
                          to: 'jfisher@xxx')
                    }
              }
       }

或者,如果您不希望将它用作作用域,则将其放入类方法中。现在RANDOM()class Thing scope :random, -> (limit = 1) { order('RANDOM()'). limit(limit) } end 一起使用。

答案 24 :(得分:0)

根据“随机”的含义和您实际想要做什么,take就足够了。

我所说的随机的意思是:

  • 您是说给我任何我不在乎位置的元素吗?那就足够了。
  • 现在,如果您的意思是“以合理的概率将任何元素给我, 重复的实验将给我带来不同的元素” 然后,使用其他方法中提到的任何一种来强制“运气” 答案。

例如,对于测试而言,无论如何都可能会随机创建示例数据,因此take绰绰有余,甚至first也是如此。

https://guides.rubyonrails.org/active_record_querying.html#take

答案 25 :(得分:0)

您可以获取所有ID的数组,然后使用sample方法返回随机元素。

Model.ids.sample