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 4和5中 ,使用PostgreSQL或SQLite ,使用RANDOM()
:
Model.order("RANDOM()").first
大概相同的将与MySQL的RAND()
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。
两种默认的采样方法( SYSTEM
和BERNOULLI
)要求您指定要返回的行数,占表中总行数的百分比。
-- 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中的一些答案,并且有两个答案:
- 兰德()有
limit
是一个明确的赢家。 - 不要使用
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和更新了穆罕默德的回答 ,以及王娜米对同样的评论和弗洛里安·皮尔兹对接受的答案的评论 – 请向他们投票!