ActiveRecord中的随机logging

我需要通过ActiveRecord从表中获取一个随机logging。 我从2006年开始跟踪Jamis Buck的例子。

不过,我也通过Googlesearchfind另一种方式(由于新的用户限制,不能使用链接进行属性设置):

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

我很好奇这里的其他人如何做到这一点,或者如果有人知道什么方法会更有效率。

我还没有find一个理想的方式做到这一点,至less有两个查询。

以下使用一个随机生成的数字(直到当前logging计数)作为偏移量

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

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

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

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

大概相同的将与MySQLRAND()

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

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

注意事项 :如果您的数据集中包含数百万条logging,则速度很慢,因此您可能需要添加limit条款。

一旦logging被删除,您的示例代码将开始performance不准确(这将不公平地倾向于使用较低ID的项目)

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

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

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

它不一定非难。

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

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

这应该performance良好,具有相同的select和支持删除行的表的可能性。 你甚至可以混合约束。

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

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

我做了一个轨道3gem处理这个:

https://github.com/spilliton/randumb

它允许你做这样的事情:

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

不build议您使用这个解决scheme,但是如果出于某种原因,您真的想随机select一条logging,而只做一个数据库查询,则可以使用Ruby Array类中的sample方法,该方法允许您select一个随机来自数组的项目。

 Model.all.sample 

这种方法只需要数据库查询,但是比起像Model.offset(rand(Model.count)).first这样的方法来说,它要慢得多,因为后者需要两个数据库查询,尽pipe后者仍然是首选。

我经常从控制台使用这个我在一个初始化程序中扩展ActiveRecord – Rails 4的例子:

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

然后,我可以打电话给Foo.random带回一个随机logging。

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) 

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

 scope :male_names, -> { where(sex: 'm') } number_of_results = 10 rand = Names.male_names.pluck(:id).sample(number_of_results) Names.where(id: rand) 

从列表中随机选取项目的Ruby方法是sample 。 为了创build一个有效的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 } 

这将是一个查询,如果模型的大小已被caching,否则两个。

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 

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

如果你使用PostgreSQL 9.5+,你可以利用TABLESAMPLE来select一个随机logging。

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

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

这需要知道表中的logging数量来select适当的百分比,这可能不容易很快find。 幸运的是, tsm_system_rows模块允许您指定要直接返回的行数。

 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采样方法是完全随机的,还是只返回随机页面的第一行。

大部分信息来自Gulcin Yildirim编写的2ndQuadrant博客文章 。

我是RoR全新的,但我得到这个为我工作:

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

它来自:

如何在Ruby中随机sorting(争夺)一个数组?

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

使用一张有10列2,500,000条logging的表格,这个取胜的方法比快速方法快了8倍(偏移量,我只在本地服务器上运行这个数字,所以数字可能会被夸大,方法是我最终会用到的,还值得注意的是,这可能会导致问题是你一次只能得到1个以上的结果,因为每个结果都是独一无二的。

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) 

对我来说很清楚

阅读所有这些并没有给我很多的信心,在Rails 5和MySQL / Maria 5.5的特殊情况下,哪一个最好。 所以我testing了〜65000条logging中的一些答案,并且有两个答案:

  1. 兰德()有limit是一个明确的赢家。
  2. 不要使用sample + sample
 def random1 Model.find(rand((Website.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) 

这个答案综合,validation和更新了穆罕默德的回答 ,以及王娜米对同样的评论和弗洛里安·皮尔兹对接受的答案的评论 – 请向他们投票!