Rails 3:获取随机记录
Posted
技术标签:
【中文标题】Rails 3:获取随机记录【英文标题】:Rails 3: Get Random Record 【发布时间】:2011-07-17 13:39:45 【问题描述】:所以,我找到了几个在 Rails 2 中查找随机记录的示例——首选方法似乎是:
Thing.find :first, :offset => rand(Thing.count)
作为一个新手,我不确定如何使用 Rails 3 中的新 find 语法来构建它。
那么,查找随机记录的“Rails 3 方式”是什么?
【问题讨论】:
重复***.com/questions/2752231/… ^^ 除了我专门寻找 Rails 3 的最佳方式,这是问题的全部目的。 rails 3 具体只是查询链:) 【参考方案1】:Thing.first(:order => "RANDOM()") # For mysql :order => "RAND()", - thanx, @DanSingerman
# Rails 3
Thing.order("RANDOM()").first
或
Thing.first(:offset => rand(Thing.count))
# Rails 3
Thing.offset(rand(Thing.count)).first
实际上,在 Rails 3 中,所有示例都可以使用。但是使用命令RANDOM
对于大表来说相当慢,但更多的是 sql 风格
UPD。您可以在索引列上使用以下技巧(PostgreSQL 语法):
select *
from my_table
where id >= trunc(
random() * (select max(id) from my_table) + 1
)
order by id
limit 1;
【讨论】:
您的第一个示例在 MySQL 中不起作用 - MySQL 的语法是 Thing.first(:order => "RAND()") (编写 SQL 而不是使用 ActiveRecord 抽象的危险) @DanSingerman,是的,它是特定于 DB 的RAND()
或 RANDOM()
。谢谢
如果索引中缺少项目,这不会产生问题吗? (如果堆栈中间的某些内容被删除,是否有机会请求它?
@VictorS,不,它不会#offset 只是转到下一个可用记录。我用 Ruby 1.9.2 和 Rails 3.1 测试了它
@JohnMerlino,是的,0 是偏移量,而不是 id。 Offet 0 表示按顺序排列的第一项。【参考方案2】:
我正在开发一个项目(Rails 3.0.15,ruby 1.9.3-p125-perf),其中 db 位于 localhost 中,并且 users 表有一点超过 10 万条记录。
使用
按 RAND() 排序
很慢
User.order("RAND(id)").first
变成
SELECT
users
.* FROMusers
ORDER BY RAND(id) LIMIT 1
响应时间为 8 到 12 秒!!
Rails 日志:
用户负载 (11030.8ms) SELECT
users
.* FROMusers
ORDER BY RAND() 限制 1 个
来自mysql的解释
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
| 1 | SIMPLE | users | ALL | NULL | NULL | NULL | NULL | 110165 | Using temporary; Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
您可以看到没有使用索引(possible_keys = NULL),创建了一个临时表,并且需要额外的 pass 才能获取所需的值(extra = Using temporary; Using文件排序)。
另一方面,通过将查询分成两部分并使用 Ruby,我们在响应时间上得到了合理的改进。
users = User.scoped.select(:id);nil
User.find( users.first( Random.rand( users.length )).last )
(;nil 用于控制台)
Rails 日志:
用户负载 (25.2ms) SELECT id FROM
users
用户负载 (0.2ms) SELECTusers
.* FROMusers
WHEREusers
.id
= 106854 限制 1
mysql 的解释证明了原因:
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| 1 | SIMPLE | users | index | NULL | index_users_on_user_type | 2 | NULL | 110165 | Using index |
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
我们现在可以只使用索引和主键,完成这项工作的速度提高了大约 500 倍!
更新:
正如icantbecool in cmets所指出的,如果表中有删除记录,上述解决方案存在缺陷。
一种解决方法可以是
users_count = User.count
User.scoped.limit(1).offset(rand(users_count)).first
转化为两个查询
SELECT COUNT(*) FROM `users`
SELECT `users`.* FROM `users` LIMIT 1 OFFSET 148794
并在大约 500 毫秒内运行。
【讨论】:
在第二个示例的“last”之后添加“.id”将避免“找不到没有 ID 的模型”错误。例如。 User.find(users.first(Random.rand(users.length)).last.id) 警告!在 MySQL 中,RAND(id)
将 NOT 为每个查询提供不同的随机顺序。如果每个查询需要不同的顺序,请使用RAND()
。
如果有记录被删除,User.find(users.first(Random.rand(users.length)).last.id) 将不起作用。 [1,2,4,5,] 并且它可能会选择 3 的 id,但不会有活动的记录关系。
另外,users = User.scoped.select(:id);nil 没有被弃用。改用这个:users = User.where(nil).select(:id)
我相信使用 Random.rand( users.length ) 作为 first 的参数是一个错误。 Random.rand 可以返回 0。当 0 用作 first 的参数时,限制设置为零并且不返回任何记录。应该使用 1 + Random(users.length) 假设 users.length > 0。【参考方案3】:
如果使用 Postgres
User.limit(5).order("RANDOM()")
如果使用 MySQL
User.limit(5).order("RAND()")
在这两种情况下,您都是从用户表中随机选择 5 条记录。这是控制台中显示的实际 SQL 查询。
SELECT * FROM users ORDER BY RANDOM() LIMIT 5
【讨论】:
【参考方案4】:我为此制作了一个 rails 3 gem,它在大型表上表现更好,并允许您链接关系和范围:
https://github.com/spilliton/randumb
(编辑):我的 gem 的默认行为现在基本上使用与上面相同的方法,但是如果您愿意,可以选择使用旧方法:)
【讨论】:
【参考方案5】:实际上发布的许多答案在相当大的表(1+ 百万行)上表现不佳。随机排序很快需要几秒钟,而在桌子上进行计数也需要很长时间。
在这种情况下适合我的解决方案是将RANDOM()
与 where 条件一起使用:
Thing.where('RANDOM() >= 0.9').take
在超过一百万行的表上,此查询通常需要不到 2 毫秒。
【讨论】:
您的解决方案的另一个优点是使用take
函数,它提供LIMIT(1)
查询但返回单个元素而不是数组。所以我们不需要调用first
在我看来,表格开头的记录以这种方式选择的概率更高,这可能不是您想要实现的。【参考方案6】:
我们来了
轨道方式
#in your initializer
module ActiveRecord
class Base
def self.random
if (c = count) != 0
find(:first, :offset =>rand(c))
end
end
end
end
用法
Model.random #returns single random object
或者第二个想法是
module ActiveRecord
class Base
def self.random
order("RAND()")
end
end
end
用法:
Model.random #returns shuffled collection
【讨论】:
Couldn't find all Users with 'id': (first, :offset=>1) (found 0 results, but was looking for 2)
如果没有任何用户并且您想要获得 2 个用户,那么您会收到错误消息。有道理。
第二种方法不适用于 postgres,但您可以改用 "RANDOM()"
...【参考方案7】:
这对我来说非常有用,但是我需要更多的灵活性,所以这就是我所做的:
案例 1:找到一条随机记录来源:trevor turk 网站 将此添加到 Thing.rb 模型
def self.random
ids = connection.select_all("SELECT id FROM things")
find(ids[rand(ids.length)]["id"].to_i) unless ids.blank?
end
然后在你的控制器中你可以调用这样的东西
@thing = Thing.random
案例 2:找到多个随机记录(不重复)来源:不记得了 我需要找到 10 个没有重复的随机记录,所以这就是我发现的工作 在您的控制器中:
thing_ids = Thing.find( :all, :select => 'id' ).map( &:id )
@things = Thing.find( (1..10).map thing_ids.delete_at( thing_ids.size * rand ) )
这将找到10条随机记录,但值得一提的是,如果数据库特别大(数百万条记录),这将不理想,并且会影响性能。 Is 将表现良好,最多可以记录几千条记录,这对我来说已经足够了。
【讨论】:
【参考方案8】:从列表中随机选择一项的 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
【讨论】:
实际上,#count
将调用数据库以获取COUNT
。如果记录已经加载,这可能是个坏主意。重构将改用#size
,因为它将决定是否应使用#count
,或者,如果记录已加载,则使用#length
。
根据您的反馈从count
切换到size
。更多信息:dev.mensfeld.pl/2014/09/…【参考方案9】:
在 Rails 5 中工作并且与 DB 无关:
这在你的控制器中:
@quotes = Quote.offset(rand(Quote.count - 3)).limit(3)
当然,您可以将其放在关注点中,如 here 所示。
app/models/concerns/randomable.rb
module Randomable
extend ActiveSupport::Concern
class_methods do
def random(the_count = 1)
records = offset(rand(count - the_count)).limit(the_count)
the_count == 1 ? records.first : records
end
end
end
那么……
app/models/book.rb
class Book < ActiveRecord::Base
include Randomable
end
然后你可以简单地使用:
Books.random
或
Books.random(3)
【讨论】:
这总是需要后续记录,至少需要记录下来(因为它可能不是用户想要的)。【参考方案10】:您可以在 ActiveRecord 中使用 sample()
例如
def get_random_things_for_home_page
find(:all).sample(5)
end
来源:http://thinkingeek.com/2011/07/04/easily-select-random-records-rails/
【讨论】:
如果你有大量记录,这是一个非常糟糕的查询,因为数据库会选择所有记录,然后 Rails 会从中选择五个记录 - 非常浪费。sample
不在 ActiveRecord 中,样本在数组中。 api.rubyonrails.org/classes/Array.html#method-i-sample
这是一种获取随机记录的昂贵方法,尤其是从大表中。 Rails 会将表中每条记录的对象加载到内存中。如果需要证明,请运行“rails 控制台”,尝试“SomeModelFromYourApp.find(:all).sample(5)”并查看生成的 SQL。
查看我的答案,它将这个昂贵的答案变成了获取多个随机记录的流线型美感。【参考方案11】:
如果使用 Oracle
User.limit(10).order("DBMS_RANDOM.VALUE")
输出
SELECT * FROM users ORDER BY DBMS_RANDOM.VALUE WHERE ROWNUM <= 10
【讨论】:
【参考方案12】:强烈推荐这个gem用于随机记录,它是专门为具有大量数据行的表设计的:
https://github.com/haopingfan/quick_random_records
所有其他答案在大型数据库中表现不佳,除了这个 gem:
-
quick_random_records 总共只花费了
4.6ms
。
-
接受的答案
User.order('RAND()').limit(10)
花费733.0ms
。
offset
方法的总成本为 245.4ms
。
User.all.sample(10)
方法成本573.4ms
。
注意:我的表只有 120,000 个用户。您拥有的记录越多,性能差异就越大。
更新:
在有 550,000 行的表上执行
Model.where(id: Model.pluck(:id).sample(10))
费用1384.0ms
gem: quick_random_records
仅花费 6.4ms
全部
【讨论】:
【参考方案13】:从表中获取多个随机记录的一种非常简单的方法。这会产生 2 个便宜的查询。
Model.where(id: Model.pluck(:id).sample(3))
您可以将“3”更改为您想要的随机记录数。
【讨论】:
不,Model.pluck(:id).sample(3) 部分并不便宜。它将读取表中每个元素的 id 字段。 有更快的与数据库无关的方法吗?【参考方案14】:我刚刚在开发一个小型应用程序时遇到了这个问题,我想从我的数据库中选择一个随机问题。我用过:
@question1 = Question.where(:lesson_id => params[:lesson_id]).shuffle[1]
它对我来说效果很好。我无法谈论大型数据库的性能如何,因为这只是一个小型应用程序。
【讨论】:
是的,这只是获取所有记录并在它们上使用 ruby 数组方法。缺点当然是它意味着将所有记录加载到内存中,然后随机重新排序它们,然后抓取重新排序数组中的第二项。如果您正在处理大型数据集,那肯定会占用大量内存。撇开次要不谈,为什么不抓住第一个元素呢? (即shuffle[0]
)
必须是随机播放[0]以上是关于Rails 3:获取随机记录的主要内容,如果未能解决你的问题,请参考以下文章