Rails 3:获取随机记录


132

因此,我找到了一些在Rails 2中查找随机记录的示例-首选方法似乎是:

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

作为新手,我不确定如何在Rails 3中使用新的find语法来构造它。

那么,查找随机记录的“路轨3种方式”是什么?



9
^^除了我特别在寻找Rails 3最佳方法外,这是问题的全部目的。
安德鲁

特定于rails 3的只是查询链:)
fl00r 2011年

Answers:


216
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对于大表,使用order 相当慢,但使用更多的sql风格

UPD。您可以在索引列(PostgreSQL语法)上使用以下技巧:

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

11
不过,您的第一个示例在MySQL中将无法使用-MySQL的语法为Thing.first(:order =>“ RAND()”)(编写SQL而不是使用ActiveRecord抽象的危险)
DanSingerman 2011年

@ DanSingerman,是的,它特定于DB RAND()RANDOM()。谢谢
fl00r 2011年

如果索引中缺少项目,这不会产生问题吗?(如果堆栈中的某些内容被删除,是否有机会提出要求?
Victor S

@VictorS,不,它不会#offset只是转到下一个可用记录。我使用Ruby 1.9.2和Rails 3.1进行了测试
SooDesuNe

1
@JohnMerlino,是0是偏移量,不是id。报价0表示根据订单的第一项。
fl00r 2014年

29

我正在开发一个项目(Rails 3.0.15,ruby 1.9.3-p125-perf),该数据库位于localhost中,而users表中的记录多于100K

使用

由RAND()订购

很慢

User.order(“ RAND(id)”)。first

变成

usersusersRAND(id)LIMIT顺序中选择。*

并需要812秒才能做出回应!!

Rails日志:

用户负载(11030.8ms)usersusersRAND()限制中选择。* FROM 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),创建了一个临时表,并且需要额外的传递才能获取所需的值(extra =使用临时的;使用filesort)。

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

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

(;无用于控制台)

Rails日志:

用户负载(25.2ms)SELECT id FROM users用户负载(0.2ms)SELECT users。* FROM usersWHERE usersid= 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毫秒内运行。


在您的第二个示例的“ last”之后添加“ .id”将避免出现“无法找到没有ID的模型”错误。例如,User.find(users.first(Random.rand(users.length))。last.id)
turing_machine 2014年

警告!在MySQL RAND(id)给你一个不同的随机顺序每个查询。RAND()如果每个查询要以不同的顺序使用。
Justin Tanner 2014年

如果删除了一条记录,则User.find(users.first(Random.rand(users.length))。last.id)将不起作用。[1,2,4,5,],它可能会选择3的id,但不会有活动的记录关系。
icantbecool

同样,不建议使用users = User.scoped.select(:id); nil。请改用此代码:users = User.where(nil).select(:id)
icantbecool

我相信使用Random.rand(users.length)作为first的参数是一个错误。Random.rand可以返回0。如果将first用作参数0,则将限制设置为零,这将不返回任何记录。假设users.length> 0,应该使用1 + Random(users.length)。–
SWoo

12

如果使用Postgres

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

如果使用MySQL

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

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

SELECT * FROM users ORDER BY RANDOM() LIMIT 5

11

为此,我制作了一个rails 3 gem,使其在大型表上表现更好,并允许您链接关系和范围:

https://github.com/spilliton/randumb

(编辑):我的gem的默认行为基本上使用与现在相同的方法,但是如果需要的话,您可以选择使用旧方法:)


6

实际上,发布的许多答案在相当大的表(1+百万行)上表现不佳。随机排序很快就需要几秒钟,而对表进行计数也需要很长时间。

在这种情况下最适合我的解决方案是在RANDOM()where条件下使用:

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

在具有一百万行的表上,此查询通常花费不到2毫秒的时间。


解决方案的另一个优点是use take函数,该函数提供LIMIT(1)查询但返回单个元素而不是数组。因此,我们不需要调用first
Piotr Galas

在我看来,以这种方式选择表开头的记录具有较高的概率,这可能不是您想要实现的。
Gorn

5

开始了

滑轨方式

#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)
2014年

如果没有任何用户,而您想获得2,则您将收到错误消息。合理。
蒂姆·克雷施默

1
第二种方法不适用于postgres,但您可以"RANDOM()"改用...
Daniel Richter

4

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

案例1:找到一个随机记录源:特雷弗·
特克站点将其添加到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将执行多达几千条对我来说足够的记录。


4

用于从列表中随机选择项目的Ruby方法是sample。想要sample为ActiveRecord 创建高效的工具,并根据前面的答案,我使用了:

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
BenMorganIO 2015年

根据您的反馈从切换countsize。更多信息,请访问:dev.mensfeld.pl/2014/09/…–
丹·科恩

3

在Rails 5中运行,并且与数据库无关:

这在您的控制器中:

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

您当然可以将其放在此处所示的问题中。

应用程序/模型/问题/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)

这总是获取后续记录,至少需要记录在案(因为这可能不是用户想要的)。
Gorn

2

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


33
如果您有大量记录,这将是一个非常糟糕的查询,因为数据库将选择ALL记录,然后Rails将从中选择5条记录-非常浪费。
DaveStephens

5
sample不在ActiveRecord中,示例在Array中。api.rubyonrails.org/classes/Array.html#method-i-sample
弗朗斯

3
这是获取随机记录(尤其是从大表中获取随机记录)的昂贵方法。Rails会将表中每条记录的一个对象加载到内存中。如果需要证明,请运行“ rails console”,尝试“ SomeModelFromYourApp.find(:all).sample(5)”,然后查看生成的SQL。
艾略特·赛克斯

1
请参阅我的答案,该答案将使这个昂贵的答案变成获取多个随机记录的简化之美。
Arcolye

1

如果使用Oracle

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

输出量

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

1

强烈建议将此gem用于随机记录,它是专为具有大量数据行的表而设计的:

https://github.com/haopingfan/quick_random_records

除以下gem之外,所有其他答案在大型数据库上的表现都很差:

  1. quick_random_records仅花费4.6ms全部。

在此处输入图片说明

  1. 可接受的答案User.order('RAND()').limit(10)费用733.0ms

在此处输入图片说明

  1. 整个offset方法成本245.4ms

在此处输入图片说明

  1. User.all.sample(10)方法成本573.4ms

在此处输入图片说明

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


更新:

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

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

在此处输入图片说明

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

在此处输入图片说明


-2

从表中获取多个随机记录的一种非常简单的方法。这使2个便宜的查询。

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

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


1
不,Model.pluck(:id).sample(3)部分并不便宜。它将读取表中每个元素的id字段。
Maximiliano Guzman 2013年

有没有更快的数据库不可知的方法?
Arcolye 2013年

-5

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

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

而且对我来说很好。我不能说大型数据库的性能如何,因为这只是一个很小的应用程序。


是的,这只是获取所有记录并在它们上使用ruby数组方法。当然,这样做的缺点是将所有记录加载到内存中,然后对它们进行随机重新排序,然后在重新排序的数组中获取第二个项目。如果您要处理大型数据集,那肯定是内存浪费。除了小问题,为什么不抢先要素呢?(即shuffle[0]
Andrew

必须洗牌[0]
马塞洛(奥地利)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.