How do I get a random list of 3 users who have at least one picture
associated with them using ActiveRecord? Sounds fun… I know :-).
A possible (untested) approach that does not rely on the usual SQL
idiom for random rows that orders the table. Assumes the set of user
IDs is not sparse:
require ‘enumerator’
ntrials = 0
max_id = User.maximum(“id”)
loop do
ntrials += 1
random_users = []
[1, rand(max_id), rand(max_id)].sort.each_cons(2) do |a, b|
user = User.find_by_sql(<<-SQL
SELECT u.*
FROM users u, pictures p
WHERE u.id BETWEEN a AND b
AND u.id = p.user_id
LIMIT 1
SQL
)
break if user.nil?
# the unless condition takes care of corner cases when
# random limits coincide
random_users << user unless random_users.include?(user)
end
redo if random_users.size < 3 && ntrials < MAX_TRIALS
end
That needs extra code for edge cases, but you see the idea.
I am sure there is a much easier way of doing it. I just really need
to get a random list of three pictures from the Picture model. (note:
right now each user only has one picture associated with him/her).
Looks like my emails are not getting to the list today. I forward
this answer to your inbox directly.
– fxn
On Oct 15, 2006, at 10:48 PM, John K. wrote:
How do I get a random list of 3 users who have at least one picture
associated with them using ActiveRecord? Sounds fun… I know :-).
A possible (untested) approach that does not rely on the usual SQL
idiom for random rows that orders the table. Assumes the set of user
IDs is not sparse:
require ‘enumerator’
ntrials = 0
max_id = User.maximum(“id”)
loop do
ntrials += 1
random_users = []
[1, rand(max_id), rand(max_id)].sort.each_cons(2) do |a, b|
user = User.find_by_sql(<<-SQL
SELECT u.*
FROM users u, pictures p
WHERE u.id BETWEEN a AND b
AND u.id = p.user_id
LIMIT 1
SQL
)
break if user.nil?
# the unless condition takes care of corner cases when
# random limits coincide
random_users << user unless random_users.include?(user)
end
redo if random_users.size < 3 && ntrials < MAX_TRIALS
end
That needs extra code for edge cases, but you see the idea.
If you’re using MySQL. (RANDOM() for Postgre and NEWID() for
SQLServer.)
You’ll still need to check for multiples. If you’re scanning by
picture, you’ll have to check the related user as well. Otherwise,
check for ‘has_picture’ or ‘picture is not null’ in the find statement
:conditions, depending on how you handle nulls in your database.
I’m guessing you don’t care too much for portability…
Sorry it’s MySQL specific - if you’re using another db, I’m sure there’s
some similar functionality for grabbing random rows, but I’m not as
familiar with the other db’s.
c.
John K. wrote:
Here are my models:
class User < ActiveRecord::Base
has_many :pictures
end
class Picture < ActiveRecord::Base
belongs_to :user
end
How do I get a random list of 3 users who have at least one picture
associated with them using ActiveRecord? Sounds fun… I know :-).
I was trying to write inline a rough idea I had in mind which was to
use rand in the Ruby side to avoid ordering the table, but messed it
up. Sorry!
What I was trying to do, really, was to pick three random ids in some
suitable range. If the table is not sparse we will fetch 3 random
records in one shot most of the time, so we’d be done with a single
find(:all) call with the random ids ORed. You still need to deal with
duplicates as in the RAND() SQL idiom, and need a lttle extra code to
loop in case some random number is not an id in the table (unlikely if
pictures are rarely deleted).
find(:all) call with the random ids ORed. You still need to deal with
duplicates as in the RAND() SQL idiom, and need a lttle extra code to
loop in case some random number is not an id in the table (unlikely if
pictures are rarely deleted).