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.* FROM users ORDER BY RAND(id) LIMIT 1

响应时间为 812 秒!!

Rails 日志:

用户负载 (11030.8ms) SELECT users.* FROM users 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) SELECT users.* FROM users WHERE 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 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:获取随机记录的主要内容,如果未能解决你的问题,请参考以下文章

随机生成的密码 Rails 3.1

Rails 4中友好id gem的随机slug url

使用 Spring 数据 JPA 获取随机记录

如何从oracle数据库中随机获取记录?

从 Google App Engine 数据存储区获取随机记录?

MYSQL:随机抽取一条数据库记录