我需要通过ActiveRecord从表中获取随机记录。我按照Jamis Buck from 2006中的示例进行了操作。
但是,我也通过Google搜索遇到了另一种方式(由于新的用户限制,无法使用链接进行归因):
rand_id = rand(Model.count)
rand_record = Model.first(:conditions => ["id >= ?", rand_id])
我很好奇其他人是如何做到的,或者有人知道哪种方式更有效率。
答案 0 :(得分:173)
在 Rails 4 和 5 中,使用 Postgresql 或 SQLite ,使用RANDOM()
:< / p>
Model.order('RANDOM()').first
据推测,对于带有RAND()
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条记录中测试了一些答案,并且有两个结果:
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'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:
答案 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
来选择随机记录。
两种默认的抽样方法(SYSTEM
和BERNOULLI
)要求您指定要返回的行数占表中总行数的百分比。
-- 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
抽样方法是完全随机的还是只是从随机页面返回第一行。
答案 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。 here和here中的更多解释。
大型表格的一个解决方案是.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
个记录中随机。
答案 21 :(得分:0)
我是RoR的新手,但我让它为我工作:
def random
@cards = Card.all.sort_by { rand }
end
它来自:
答案 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