49

User.find(:all, :order => "RANDOM()", :limit => 10) was the way I did it in Rails 3.

User.all(:order => "RANDOM()", :limit => 10) is how I thought Rails 4 would do it, but this is still giving me a Deprecation warning:

DEPRECATION WARNING: Relation#all is deprecated. If you want to eager-load a relation, you can call #load (e.g. `Post.where(published: true).load`). If you want to get an array of records from a relation, you can call #to_a (e.g. `Post.where(published: true).to_a`).
justindao
  • 2,273
  • 4
  • 18
  • 34

10 Answers10

120

You'll want to use the order and limit methods instead. You can get rid of the all.

For PostgreSQL and SQLite:

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

Or for MySQL:

User.order("RAND()").limit(10)
Grisha Levit
  • 8,194
  • 2
  • 38
  • 53
Dylan Markow
  • 123,080
  • 26
  • 284
  • 201
  • 13
    Just a quick note, I had to use: `User.order("RAND()").limit(10)` as I am using a MySQL database. – backwardm May 12 '14 at 22:20
  • 1
    Note that this is O(n^2) and bound to be unacceptable for any table bigger than a few rows. See the .offset(rand()) method by @maurimiranda below for the only sane solution in this thread. – Matthias Winkelmann Mar 06 '17 at 00:35
  • `User.order("RANDOM()").limit(10)` worked for me on Postrgres and Rails 5. – Simon Cooper Apr 09 '17 at 09:38
  • 4
    For anybody who comes here from Rails 5.2+ you should wrap "RANDOM()" or "RAND()" in `Arel.sql()` because it's not on the safe value list. However, it's safe to you, I assume. – Jordon Bedwell Feb 17 '18 at 19:09
  • An example with Arel.sql. `User.order(Arel.sql('random()')).limit(10)` – Cody Elhard Aug 21 '19 at 14:10
40

As the random function could change for different databases, I would recommend to use the following code:

User.offset(rand(User.count)).first

Of course, this is useful only if you're looking for only one record.

If you wanna get more that one, you could do something like:

User.offset(rand(User.count) - 10).limit(10)

The - 10 is to assure you get 10 records in case rand returns a number greater than count - 10.

Keep in mind you'll always get 10 consecutive records.

maurimiranda
  • 509
  • 4
  • 4
  • 6
    +1 Not only is the `offset` approach more versatile than the accepted answer but it's also faster when there are a lot of records. This is because `ORDER BY RANDOM() LIMIT $1` will **sort the entire table** then pick the first _n_ items. The sorting operation is likely `O(n * log n)` and sorting a large table not only reads that table but also involves reading and writing temporary files. – Dennis Apr 16 '16 at 16:59
  • This worked for me, whereas `User.order("RANDOM()").limit(10)` did not; it always returned the same record. I'm using a **Postgresql** database and running **Rails 5**. – sambecker Aug 22 '16 at 15:18
  • This would be the correct answer if the question was specific to finding *a* random record. But the question asks about finding random records in general. Finding a set of rows in order at random isn't really the same. – pixatlazaki Jul 18 '17 at 00:05
  • 1
    Please pay attention your table cannot have delete record, otherwise ID is not in seq. – Leonardo Wong Oct 16 '17 at 14:38
  • It should be `User.offset(rand(User.count - 10)).limit(10)` instead of `rand(User.count) - 10` otherwise there is a chance you will end up with a negative index, which raises a `PG::InvalidRowCountInResultOffsetClause`. – causztic Aug 19 '20 at 09:24
23

I think the best solution is really ordering randomly in database. But if you need to avoid specific random function from database, you can use pluck and shuffle approach.

For one record:

User.find(User.pluck(:id).shuffle.first)

For more than one record:

User.where(id: User.pluck(:id).sample(10))
Prodis
  • 506
  • 6
  • 8
  • 2
    Nice. Even shorter: `User.find( User.ids.shuffle.first )` – Arta Jan 24 '17 at 16:40
  • `User.pluck(:id).shuffle` will load whole database ids in memory. Do not do this for tables with a lot of records – AyJay Jul 07 '21 at 12:12
13

I would suggest making this a scope as you can then chain it:

class User < ActiveRecord::Base
  scope :random, -> { order(Arel::Nodes::NamedFunction.new('RANDOM', [])) }
end 

User.random.limit(10)
User.active.random.limit(10)
Kyle Decot
  • 20,715
  • 39
  • 142
  • 263
8

While not the fastest solution, I like the brevity of:

User.ids.sample(10)

The .ids method yields an array of User IDs and .sample(10) picks 10 random values from this array.

evaneykelen
  • 472
  • 3
  • 5
7

Strongly Recommend this gem for random records, which is specially designed for table with lots of data rows:

https://github.com/haopingfan/quick_random_records

All other answers perform badly with large database, except this gem:

  1. quick_random_records only cost 4.6ms totally.

enter image description here

  1. the accepted answer User.order('RAND()').limit(10) cost 733.0ms.

enter image description here

  1. the offset approach cost 245.4ms totally.

enter image description here

  1. the User.all.sample(10) approach cost 573.4ms.

enter image description here

Note: My table only has 120,000 users. The more records you have, the more enormous the difference of performance will be.


UPDATE:

Perform on table with 550,000 rows

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

enter image description here

  1. gem: quick_random_records only cost 6.4ms totally

enter image description here

Derek Fan
  • 817
  • 11
  • 10
1

For MYSQL this worked for me:

User.order("RAND()").limit(10)
beesasoh
  • 2,075
  • 2
  • 15
  • 17
0

You could call .sample on the records, like: User.all.sample(10)

s2t2
  • 2,462
  • 5
  • 37
  • 47
  • 3
    this would take a long time because we're dealing with Ruby objects. If these objects are in the hundreds of thousands, this may break – Igbanam Nov 08 '18 at 17:36
0

The answer of @maurimiranda User.offset(rand(User.count)).first is not good in case we need get 10 random records because User.offset(rand(User.count) - 10).limit(10) will return a sequence of 10 records from the random position, they are not "total randomly", right? So we need to call that function 10 times to get 10 "total randomly".

Beside that, offset is also not good if the random function return a high value. If your query looks like offset: 10000 and limit: 20 , it is generating 10,020 rows and throwing away the first 10,000 of them, which is very expensive. So call 10 times offset.limit is not efficient.

So i thought that in case we just want to get one random user then User.offset(rand(User.count)).first maybe better (at least we can improve by caching User.count).

But if we want 10 random users or more then User.order("RAND()").limit(10) should be better.

Lam Phan
  • 3,405
  • 2
  • 9
  • 20
-4

Here's a quick solution.. currently using it with over 1.5 million records and getting decent performance. The best solution would be to cache one or more random record sets, and then refresh them with a background worker at a desired interval.

Created random_records_helper.rb file:

module RandomRecordsHelper

 def random_user_ids(n)
    user_ids = []
    user_count = User.count
    n.times{user_ids << rand(1..user_count)}
    return user_ids
 end

in the controller:

@users = User.where(id: random_user_ids(10))

This is much quicker than the .order("RANDOM()").limit(10) method - I went from a 13 sec load time down to 500ms.

Kevin Francis
  • 54
  • 1
  • 3
  • 1
    Of course, this only works if your ids are auto-incrementing integers, start with 1 and there are no holes in the sequence. – Sergio Tulentsev Jun 07 '16 at 15:40
  • 1
    Gotta downvote this because of the same sentiments as @SergioTulentsev. If you've ever destroyed any records then this method is unreliable, being that you could ask for 10 records and potentially not actually get 10 records. The speediness is nice but speed without correct results has no value, while correct results (even if they're slower than you'd like) does still have value. – jeffdill2 Oct 19 '16 at 13:28