Rails 3: 获取随机记录

因此,我找到了几个在 Rails 2中找到随机记录的例子——首选的方法似乎是:

Thing.find :first, :offset => rand(Thing.count)

作为一个新手,我不知道如何使用 Rails 3中的新 find 语法来构建这个代码。

那么,找到一个随机记录的“ Rails 3 Way”是什么呢?

68292 次浏览
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 风格

可以对索引列(PostgreSQL 语法)使用以下技巧:

select *
from my_table
where id >= trunc(
random() * (select max(id) from my_table) + 1
)
order by id
limit 1;

我做了一个 Rails 3 gem,它可以在大型表上表现得更好,并且允许你链接关系和范围:

Https://github.com/spilliton/randumb

(edit) : gem 的默认行为基本上使用了与上面相同的方法,但是如果您愿意,可以选择使用旧的方法:)

这对我非常有用,但是我需要更多的灵活性,所以这就是我所做的:

案例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: 发现多个随机记录(无重复) < em > 来源: 不记得了
我需要找到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条随机记录,但是值得一提的是,如果数据库特别大(数百万条记录) ,这将是不理想的,并且性能将受到阻碍。是将执行良好的多达几千记录,这对我来说是足够的。

我正在从事一个项目(Rails 3.0.15,ruby 1.9.3-p125-perf) ,其中的数据库是在 本地主机和用户表有一点多于 10万张唱片

吸毒

RAND ()订购

很慢

Order (“ RAND (id)”) . first

变成了

users中按兰德(id)限制1选择 users.*

并采取从 812秒的反应! !

Rails 日志:

用户负载(11030.8 ms)按 RAND ()顺序从 users中选择 users。 * 限制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 |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+

您可以看到没有使用索引(可能的 _ key = NULL) ,创建了一个临时表,并且需要额外的传递来获取所需的值(使用临时的; 使用文件排序)。

另一方面,通过将查询分成两部分并使用 Ruby,我们在响应时间方面有了合理的改进。

users = User.scoped.select(:id);nil
User.find( users.first( Random.rand( users.length )).last )

(; 控制台使用为 nil)

Rails 日志:

用户负载(25.2 ms) SELECT id FROM users用户负载(0.2 ms) SELECT 从 users开始,其中 users.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 在评论中指出的那样,如果表中有删除记录,上述解决方案存在缺陷。

一个解决方案

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毫秒。

可以在 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/

我只是在开发一个小型应用程序时遇到了这个问题,我想从数据库中选择一个随机问题。我用:

@question1 = Question.where(:lesson_id => params[:lesson_id]).shuffle[1]

对我来说效果很好。由于这只是一个小型应用程序,我不能谈论大型 DBs 的性能如何。

开始了

铁轨

#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

从表中获取多个随机记录的一种非常简单的方法。

Model.where(id: Model.pluck(:id).sample(3))

您可以将“3”更改为所需的随机记录数。

许多张贴的答案实际上在相当大的表(1 + 百万行)上表现不佳。随机排序需要几秒钟的时间,在桌子上计数也需要相当长的时间。

在这种情况下,一个对我有效的解决方案是使用带有 where 条件的 RANDOM():

Thing.where('RANDOM() >= 0.9').take

对于超过一百万行的表,这个查询通常只需要不到2毫秒。

从列表中随机选择项的 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 }

如果使用 Postgres

User.limit(5).order("RANDOM()")

如果使用 MySQL

User.limit(5).order("RAND()")

在这两种情况下,您都是从 Users 表中随机选择5条记录。下面是控制台中显示的实际 SQL 查询。

SELECT * FROM users ORDER BY RANDOM() LIMIT 5

如果使用甲骨文

User.limit(10).order("DBMS_RANDOM.VALUE")

输出

SELECT * FROM users ORDER BY DBMS_RANDOM.VALUE WHERE ROWNUM <= 10

在 Rails 5中工作,并且与 DB 无关:

在你的控制器中:

@quotes = Quote.offset(rand(Quote.count - 3)).limit(3)

当然,您可以将此放在如 给你所示的关注点中。

应用程序/模型/关注点/随机

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/model/book.rb

class Book < ActiveRecord::Base
include Randomable
end

然后你可以简单地使用:

Books.random

或者

Books.random(3)

强烈推荐随机记录使用这个 gem,它是专门为拥有大量数据行的表设计的:

Https://github.com/haopingfan/quick_random_records

所有其他答案在大型数据库中的表现都很糟糕,除了这个 gem:

  1. 总共只花费 4.6ms

enter image description here

  1. 接受的答案 User.order('RAND()').limit(10)成本 733.0ms

enter image description here

  1. offset方法完全花费 245.4ms

enter image description here

  1. User.all.sample(10)接近成本 573.4ms

enter image description here

注意: 我的表只有120,000个用户。你拥有的记录越多,性能的差异就越大。


更新:

在具有550,000行的表上执行

  1. Model.where(id: Model.pluck(:id).sample(10))成本 1384.0ms

enter image description here

  1. gem: quick_random_records只花了 6.4ms的全部费用

enter image description here

如果存在数百万条记录,那么通过 RDBMS 进行随机排序可能会非常昂贵。为了简化这一点,您可以这样限制排序记录的数量(PostgreSQL 语法) :

module ActiveRecord
class Base
def self.sample
where(
"id >= TRUNC(RANDOM() * (SELECT MAX(id) FROM #{table_name}) + 1)"
).order(:id).first
end
end
end

然后是 User.sample

在 id 均匀分布的情况下,这将更随机地工作