我需要通过ActiveRecord从表中获取随机记录.我从2006年开始跟随Jamis Buck的例子.
但是,我也通过Google搜索遇到了另一种方式(由于新的用户限制,无法使用链接进行归因):
rand_id = rand(Model.count) rand_record = Model.first(:conditions => ["id >= ?", rand_id])
我很好奇这里的其他人是如何做到的,或者是否有人知道哪种方式会更有效率.
在Rails 4和5中,使用Postgresql或SQLite,使用RANDOM()
:
Model.order('RANDOM()').first
想必同样会为工作的MySQL与RAND()
Model.order('RAND()').first
这比接受的答案中的方法快约2.5倍.
警告:对于具有数百万条记录的大型数据集,这种情况很慢,因此您可能希望添加一个limit
子句.
如果没有至少两个查询,我还没有找到理想的方法.
以下使用随机生成的数字(最多为当前记录计数)作为偏移量.
offset = rand(Model.count) # Rails 4 rand_record = Model.offset(offset).first # Rails 3 rand_record = Model.first(:offset => offset)
说实话,我刚刚使用ORDER BY RAND()或RANDOM()(取决于数据库).如果您没有性能问题,这不是性能问题.
一旦删除记录,您的示例代码将开始表现不正确(它将不公平地支持具有较低ID的项目)
你最好在数据库中使用随机方法.这取决于哪个DB你使用的有所不同,但:为了=>"RAND()"的作品MySQL和:为了=>"RANDOM()"工程的Postgres
Model.first(:order => "RANDOM()") # postgres example
在具有+ 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表".那不行.
它不一定很难.
ids = Model.pluck(:id) random_model = Model.find(ids.sample)
pluck
返回表中所有id的数组.在sample
该阵列上的方法,则返回从所述阵列的随机ID.
这应该表现良好,具有相同的选择概率和支持已删除行的表.你甚至可以将它与约束混合在一起.
User.where(favorite_day: "Friday").pluck(:id)
从而挑选一个喜欢星期五而不是任何用户的随机用户.
我做了一个rails 3 gem来处理这个问题:
https://github.com/spilliton/randumb
它允许你做这样的事情:
Model.where(:column => "value").random(10)
不建议您使用此解决方案,但如果由于某种原因您真的想在只进行一次数据库查询时随机选择记录,则可以使用Ruby Array类中的sample
方法,该方法允许您选择随机项从一个数组.
Model.all.sample
此方法仅需要数据库查询,但它比Model.offset(rand(Model.count)).first
需要两个数据库查询的替代方法要慢得多,尽管后者仍然是首选.
我经常在控制台中使用它,我在初始化程序中扩展了ActiveRecord - Rails 4示例:
class ActiveRecord::Base def self.random self.limit(1).offset(rand(self.count)).first end end
然后我可以打电话Foo.random
给一个随机记录.
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)
阅读所有这些内容并没有让我对使用Rails 5和MySQL/Maria 5.5在特定情况下最适合哪些内容有很大的信心.所以我在约65000条记录上测试了一些答案,并且有两个收获:
兰德()与a limit
是一个明显的赢家.
不要用pluck
+ sample
.
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的答案,以及Nami WANG对此的评论以及Florian Pilz对接受答案的评论 - 请向他们发送投票!