ruby on rails - ActiveRecord中的随机记录




ruby-on-rails random (14)

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)

我需要通过ActiveRecord从表中获取随机记录。 我从2006年开始跟随来自Jamis Buck的例子。

不过,我也通过Google搜索找到另一种方式(由于受到新的用户限制,无法链接链接):

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

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


.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中禁用缓存以使这些基准可靠是没有简单的方法。


一旦记录被删除,您的示例代码就会开始表现不准确(这会不公平地倾向于使用较低ID的项目)

在数据库中使用随机方法可能会更好。 这些取决于你使用的是哪个数据库,但是:order =>“RAND()”适用于mysql和:order =>“RANDOM()”适用于postgres

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

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

Model.all.sample

这种方法只需要数据库查询,但是它比像Model.offset(rand(Model.count)).first类的替代方法慢得多,后者需要两个数据库查询,尽管后者仍然是首选。


Rails 45中 ,使用PostgreSQLSQLite ,使用RANDOM()

Model.order("RANDOM()").first

大概这同样适用于使用RAND() MySQL

Model.order("RAND()").first

这比接受的答案中的方法大约2.5倍

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


在MySQL 5.1.49上对这两种方法进行基准测试,在具有+5百万记录的产品表上使用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表”。 这是行不通的。


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

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

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

它不一定非常难。

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

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

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

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

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


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

User.all.sample

会返回这样的内容:

#<User id: 25, name: "John Doe", email: "[email protected]", created_at: "2018-04-16 19:31:12", updated_at: "2018-04-16 19:31:12">



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

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

然后,我可以打电话给Foo.random记录一个随机记录。


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

使用一个包含10列2,500,000个条目的表格,获胜者的取胜方法是比亚军快8倍的偏移方式(偏移量。我只在本地服务器上运行这个数字,这样数字可能会膨胀,但是它的大小足以使摘取方法是我最终使用的方法,同样值得注意的是,这可能会导致问题,因为这些方法中的每一个都会是独一无二的,而且随机性更强。

Pluck在我的25,000,000行表上运行100次编辑:实际上这次包括循环中的pluck,如果我把它拿出来,它的运行速度和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)

该怎么做:

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

对我来说很清楚





random