2010-05-02 74 views
133

我需要通过ActiveRecord从表中获取随机记录。我遵循Jamis Buck from 2006的示例。ActiveRecord中的随机记录

不过,我也可以通过谷歌搜索遇到的另一种方式(不能用链接属性,由于新的用户限制):

rand_id = rand(Model.count) 
rand_record = Model.first(:conditions => ["id >= ?", rand_id]) 

我很好奇别人怎样在这里已经做了它或者如果有人知道什么方式会更有效率。

+2

2点可能有助于答案。 1.您的ID是多么均匀分布的,它们是连续的吗? 2.它需要多少随机性?足够好随机,还是真正的随机? – Michael 2010-05-02 02:42:10

+0

它们是由activerecord自动生成的顺序ids,它必须足够好。 – jyunderwood 2010-05-02 03:04:30

+1

然后你提出的解决方案是接近理想的:)我会使用“SELECT MAX(id)FROM table_name”而不是COUNT(*),因为它会更好地处理删除的行,否则,其余的很好。简而言之,如果“足够好”是可以的,那么你只需要有一种方法,假设一个分布接近你实际拥有的分布。如果它是统一的,就像你所说的那样,简单的兰德效果很好。 – Michael 2010-05-02 03:39:33

回答

122

我还没有找到一个没有至少两个查询的理想方式。

以下使用一个随机生成的数字(最多当前记录数)作为偏移量。老实说,我刚刚使用ORDER BY RAND()或RANDOM()(取决于数据库)。如果您没有性能问题,这不是性能问题。

+2

代码'Model.find(:offset => offset).first'会抛出错误。我认为'Model.first(:offset => offset)'可能表现更好。 – 2010-05-03 00:30:59

+1

是的,我一直在使用Rails 3,并不断对版本之间的查询格式感到困惑。 – 2010-05-03 00:39:48

+7

请注意,对于大型数据集,使用偏移量非常缓慢,因为它实际上需要索引扫描(或者表扫描,如果使用像InnoDB那样的聚集索引)。换句话说,它是O(N)操作,但是“WHERE id> =#{rand_id} ORDER BY id ASC LIMIT 1”是O(log N),这更快。 – kenn 2011-05-06 01:02:16

71

你的示例代码将开始表现不准确,一旦记录被删除(这将有利于不公平的低IDS项目)

你可能最好使用您的数据库中随机方法。这取决于哪个DB你使用的有所不同,但:为了=> “RAND()” 的作品MySQL和:为了=> “RANDOM()” 的作品Postgres的

Model.first(:order => "RANDOM()") # postgres example 
+7

随着数据的增加,MySQL的ORDER BY RAND()会以可怕的运行时间结束。它是不可维护的(取决于时间要求),甚至只有成千上万行。 – Michael 2010-05-02 02:45:13

+0

迈克尔提出了一个伟大的观点(对于其他数据库也是如此)。通常从大表中选择随机行不是您想要在动态操作中执行的操作。缓存是你的朋友。反思你想要完成的事情也许不是一个坏主意。 – semanticart 2010-05-02 02:52:14

+1

在大约一百万行的表上对MySQL中的RAND()进行排序是slooooooooooooooooooooow。 – Subimage 2011-09-08 19:50:34

27

标杆MySQL的49年1月5日,红宝石1.9.2p180这两种方法在产品表+ 500万个记录:

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表”。这是行不通的。

+1

对于那些正在寻找更多测试的人来说,真正的随机方法需要多长时间:我在一张有250k条目的表上尝试过'Thing.order(“RANDOM()”)。 - 查询在半秒内完成。 (PostgreSQL 9.0,REE 1.8.7,2 x 2.66 GHz内核)对于我来说,这足够快,因为我正在做一次性“清理”。 – 2011-06-16 13:12:59

+6

Ruby的rand方法只返回一个小于指定数字的值,所以你需要'rand_id = rand(Product.count)+ 1',否则你永远不会得到最后一条记录。 – Ritchie 2012-02-24 12:34:21

+4

注意:如果您删除了表格中的一行,“random1”将不起作用。 (Count将小于最大ID,并且您将永远无法选择具有高ID的行)。 – Nicholas 2012-12-09 22:26:14

12

我做了一个轨道3宝石处理这个问题:

https://github.com/spilliton/randumb

它可以让你做的东西是这样的:

Model.where(:column => "value").random(10) 
+7

在这个gem的文档中,他们解释了_“randumb简单地为你的查询添加了一个额外的'ORDER BY RANDOM()'(或'RAND()')。” - 因此,评论中提到的糟糕的性能到@semanticart的答案也适用于使用这个宝石时。但至少它是独立于数据库的。 – Nicolas 2014-01-16 13:20:29

9

它不告知您使用此解决方案,但如果由于某种原因你真的想要随机选择一条记录,同时只做一个数据库查询,你可以使用方法从Ruby Array class,这允许哟你从数组中选择一个随机项目。

Model.all.sample 

此方法只需要数据库查询,但它不是像Model.offset(rand(Model.count)).first需要两个数据库查询的替代显著慢,尽管后者仍然优选的。在Postgres的

+81

不要这样做。永远。 – Zabba 2012-10-16 05:06:14

+5

如果数据库中有100k行,则所有这些行都必须加载到内存中。 – 2012-11-13 22:10:23

+3

当然不推荐用于生产实时代码,但我喜欢这个解决方案,它非常清楚用于特殊情况,比如_seeding_具有假值的数据库。 – fguillen 2012-12-09 10:55:01

5

一个查询:

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) 
+0

不需要-1,兰德计数到num - 1 – anemaria20 2016-11-22 17:31:35

+0

谢谢,更改:+1: – 2016-11-22 20:34:51

16

它并不一定是困难的。

ids = Model.pluck(:id) 
random_model = Model.find(ids.sample) 

pluck返回表中所有id的数组。数组上的sample方法从数组中返回一个随机ID。

这应该表现良好,对于删除行的表有相同的选择和支持概率。你甚至可以将它与约束混合。

User.where(favorite_day: "Friday").pluck(:id) 

从而挑选喜欢周五而不是任何用户的随机用户。

+7

这是干净的,适用于小表或一次性使用,只是注意它不会缩放。在3M桌子上,在MariaDB上为我抽取ID约需15秒。 – mahemoff 2014-06-07 14:54:17

+1

这是一个很好的观点。您是否找到了更快的替代解决方案,同时保持相同的品质? – 2014-06-21 16:16:45

+0

接受的胶印解决方案是否保持相同的品质? – mahemoff 2014-06-21 21:34:09

0

我是全新的,以回报率,但我得到这个工作对我来说:

def random 
    @cards = Card.all.sort_by { rand } 
end 

它来自:

How to randomly sort (scramble) an array in Ruby?

+4

它的坏处是它会从数据库中加载所有卡。在数据库中执行它更有效率。 – 2013-11-09 10:28:20

+0

你也可以用'array.shuffle'洗牌数组。无论如何,请注意,因为'Card.all'会将所有卡片记录加载到内存中,这会导致我们正在讨论的对象越多效率越低。 – 2013-11-11 13:20:50

7

我用这个经常从我扩展控制台初始化器中的ActiveRecord - Rails 4示例:

class ActiveRecord::Base 
    def self.random 
    self.limit(1).offset(rand(self.count)).first 
    end 
end 

然后我可以调用Foo.random带回一条随机记录。

+1

你需要限制(1)吗? “ActiveRecord#first”应该足够聪明来做到这一点。 – tokland 2014-11-14 13:41:30

132

导轨4和,使用PostgreSQL的SQLite的,使用RANDOM()

Model.order("RANDOM()").first 

大概相同将为工作的MySQLRAND()

Model.order("RAND()").first 

is about 2.5 times更快比在accepted answer的方法。

警告:如果您的大型数据集含有数百万条记录,那么这很慢,因此您可能需要添加limit子句。

+3

“Random()”也适用于sqlite,所以对于我们这些仍在开发sqlite并在生产环境中运行postgres的人来说,您的解决方案可以在两种环境中使用。 – wuliwong 2014-11-29 16:29:43

+3

我为此创建了一个[基准](https://gist.github.com/panmari/73a2c203d24e7e9461d1)。在Postgresql 9.4上,这个答案的处理速度大约快两倍。 – panmari 2015-01-11 20:58:21

+3

貌似不建议在MySQL http://www.webtrenches.com/post.cfm/avoid-rand-in-mysql – 2015-10-15 17:30:34

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) 
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 } 

这将是一个查询如果模型的大小已经被缓存,另外两个查询。

1

对于MySQL数据库尝试:Model.order( “RAND()”)第一

+0

这不工作在MySQL ..你应该包括至少是什么数据库引擎是这个假设与 – 2016-07-04 14:34:17

+0

工作对不起,有错字。现在修复。应该为mysql工作(只) – 2016-07-05 12:00:26

1

的Rails 4.2和Oracle

对于Oracle您可以在模型中设置一个范围,像这样:

scope :random_order, -> {order('DBMS_RANDOM.RANDOM')} 

scope :random_order, -> {order('DBMS_RANDOM.VALUE')} 

然后FO RA样称呼它:

Model.random_order.take(10) 

Model.random_order.limit(5) 
当然

你也可以下订单没有范围,像这样:

Model.all.order('DBMS_RANDOM.RANDOM') # or DBMS_RANDOM.VALUE respectively 
+0

你可以用'order('random()''和MySQL'with'order('rand()')'用postgres做这个,这绝对是最好的答案。 – jrochkind 2016-04-20 18:27:42

1

如果你正在使用PostgreSQL 9.5 +,你可以利用TABLESAMPLE来选择一个随机记录。

这两种默认采样方法(SYSTEMBERNOULLI)要求您指定要返回的行数,占表中总行数的百分比。

-- 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采样方法将完全是随机的,或者它只是从随机页面返回第一行。

此信息的多数取自2ndQuadrant blog post written by Gulcin Yildirim

1

看到这么多答案后,我决定在我的PostgreSQL(9.6.3)数据库中对它们进行基准测试。我使用了一个更小的100,000张表格,并且摆脱了Model.order(“RANDOM()”),因为它已经慢了两个数量级。

使用一个包含10列的2,500,000个条目的表格,获胜者的胜利者是拔取方法,其速度比亚军快了近8倍(偏移量,我只在本地服务器上运行这个数字,足以说明采摘方法是我最终使用的方式,同样值得注意的是,这可能会导致问题是,您一次只采集1个以上的结果,因为其中每一个结果都是独一无二的。在我的25,000,000行表上赢得100次运行 编辑:实际上这次包括循环中的插入,如果我把它拿出来,它的运行速度和简单的迭代一样快,但是它占用相当数量的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) 

这是该数据在我的10万行的表运行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) 
0

什么做:

rand_record = Model.find(Model.pluck(:id).sample) 

对我来说是非常清楚的

2

阅读所有这些并没有给我很多的信心,在Rails 5和MySQL/Maria 5.5的特殊情况下,哪一个最好。所以,我测试了一些关于〜65000所记载的答案,并有两个拿走的题:

  1. RAND()与limit是一个明显的赢家。
  2. 请勿使用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,以及奈王的上公认的答案相同,弗洛里安皮尔磁的评论评论 - 请发选票给他们!

0

我尝试这个山姆的例子在我的应用程序使用rails 4.2.8的基准(我把1..Category.count随机,因为如果随机采取0它会产生一个错误(ActiveRecord :: RecordNotFound :找不到类别'id'= 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)