当前位置:  开发笔记 > 编程语言 > 正文

ActiveRecord中的随机记录

如何解决《ActiveRecord中的随机记录》经验,为你挑选了10个好方法。

我需要通过ActiveRecord从表中获取随机记录.我从2006年开始跟随Jamis Buck的例子.

但是,我也通过Google搜索遇到了另一种方式(由于新的用户限制,无法使用链接进行归因):

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

我很好奇这里的其他人是如何做到的,或者是否有人知道哪种方式会更有效率.



1> Mohamad..:

Rails 45中,使用PostgresqlSQLite,使用RANDOM():

Model.order('RANDOM()').first

想必同样会为工作的MySQLRAND()

Model.order('RAND()').first

这比接受的答案中的方法约2.5倍.

警告:对于具有数百万条记录的大型数据集,这种情况很慢,因此您可能希望添加一个limit子句.


我针对接受的答案创建了[基准](https://gist.github.com/panmari/73a2c203d24e7e9461d1).在Postgresql 9.4上,这个答案的方法大约快了两倍.
"Random()"也适用于sqlite,因此对于我们这些仍然在sqlite上开发并在生产中运行postgres的人来说,您的解决方案适用于这两种环境.
看起来不推荐在mysql http://www.webtrenches.com/post.cfm/avoid-rand-in-mysql

2> Toby Hede..:

如果没有至少两个查询,我还没有找到理想的方法.

以下使用随机生成的数字(最多为当前记录计数)作为偏移量.

offset = rand(Model.count)

# Rails 4
rand_record = Model.offset(offset).first

# Rails 3
rand_record = Model.first(:offset => offset)

说实话,我刚刚使用ORDER BY RAND()或RANDOM()(取决于数据库).如果您没有性能问题,这不是性能问题.


请注意,偏移方法只会产生一个随机找到的数据点(第一个,后面的所有数据点仍按ID排序).如果您需要多个随机选择的记录,您必须多次使用此方法或使用数据库提供的随机顺序方法,即`Thing.order("RANDOM()").limit(100)`用于100个随机选择的条目.(请注意,它是PostgreSQL中的`RANDOM()`和MySQL中的`RAND()`...不像你想要的那样便携.)
请注意,对于大型数据集,使用偏移非常慢,因为它实际上需要索引扫描(或表扫描,以防像InnoDB一样使用聚簇索引).换句话说,它是O(N)操作,但是"WHERE id> =#{rand_id} ORDER BY id ASC LIMIT 1"是O(log N),这要快得多.
在Rails 4上对我不起作用.使用`Model.offset(offset).first`.
代码`Model.find(:offset => offset).first`将抛出错误.我认为`Model.first(:offset => offset)`可能表现得更好.

3> semanticart..:

一旦删除记录,您的示例代码将开始表现不正确(它将不公平地支持具有较低ID的项目)

你最好在数据库中使用随机方法.这取决于哪个DB你使用的有所不同,但:为了=>"RAND()"的作品MySQL和:为了=>"RANDOM()"工程的Postgres

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


不再工作了.使用`Model.order("RANDOM()").first`代替.
随着数据的增加,MySQL的ORDER BY RAND()最终会出现在可怕的运行时.即使从几千行开始,它也是不可维护的(取决于时间要求).

4> dkam..:

在具有+ 500万条记录的产品表上对MySQL 5.1.49,Ruby 1.9.2p180上的这两种方法进行基准测试:

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表".那不行.


Ruby的rand方法返回一个少于指定数字的方法,因此你需要`rand_id = rand(Product.count)+ 1`或者你永远不会得到最后一条记录.
注意如果删除表中的行,`random1`将不起作用.(计数将小于最大ID,您将永远无法选择具有高ID的行).

5> Niels B...:

它不一定很难.

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

pluck返回表中所有id的数组.在sample该阵列上的方法,则返回从所述阵列的随机ID.

这应该表现良好,具有相同的选择概率和支持已删除行的表.你甚至可以将它与约束混合在一起.

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

从而挑选一个喜欢星期五而不是任何用户的随机用户.


这很干净,适用于小桌子或一次性使用,请注意它不会缩放.在3M桌上,在MariaDB上为我提取ID大约需要15秒.
那是个很好的观点.您是否找到了更快的替代解决方案,同时保持了相同的品质?

6> spilliton..:

我做了一个rails 3 gem来处理这个问题:

https://github.com/spilliton/randumb

它允许你做这样的事情:

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


在这个gem的文档中,他们解释了_"randumb简单地为你的查询添加了一个额外的`ORDER BY RANDOM()`(或者`s的`RAND()`."_ - 因此,评论中提到的关于性能不佳的评论@semanticart的答案也适用于使用此gem时.但至少它是DB独立的.

7> Ryan Atallah..:

不建议您使用此解决方案,但如果由于某种原因您真的想在只进行一次数据库查询时随机选择记录,则可以使用Ruby Array类中的sample方法,该方法允许您选择随机项从一个数组.

Model.all.sample

此方法仅需要数据库查询,但它比Model.offset(rand(Model.count)).first需要两个数据库查询的替代方法要慢得多,尽管后者仍然是首选.


不要这样做.永远.
请 - 永远不要说永远.如果表很小,这是开发时调试的一个很好的解决方案.(如果您正在采样,调试很可能是用例).
如果数据库中有100k行,则必须将所有这些行加载到内存中.
当然不建议用于生产实时代码,但是我喜欢这个解决方案,很明显可以用于特殊情况,例如_seeding_具有假值的数据库.

8> Knotty66..:

我经常在控制台中使用它,我在初始化程序中扩展了ActiveRecord - Rails 4示例:

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

然后我可以打电话Foo.random给一个随机记录.



9> Thomas Klemm..:

Postgres中的一个问题:

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)



10> Sam..:

阅读所有这些内容并没有让我对使用Rails 5和MySQL/Maria 5.5在特定情况下最适合哪些内容有很大的信心.所以我在约65000条记录上测试了一些答案,并且有两个收获:

    兰德()与a limit是一个明显的赢家.

    不要用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的答案,以及Nami WANG对此的评论以及Florian Pilz对接受答案的评论 - 请向他们发送投票!

推荐阅读
mobiledu2402851373
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有