Случайная запись в ActiveRecord - PullRequest
144 голосов
/ 02 мая 2010

Мне нужно получить случайную запись из таблицы через ActiveRecord.Я следовал примеру из Jamis Buck из 2006 .

Однако я также натолкнулся на поиск в Google (не могу связать ссылку с новым пользователемограничения):

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

Мне любопытно, как другие здесь сделали это или кто-нибудь знает, какой путь будет более эффективным.

Ответы [ 24 ]

176 голосов
/ 30 августа 2014

In Rails 4 и 5 , используя Postgresql или SQLite , используя RANDOM():

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

Предположительно, то же самое будет работать для MySQL с RAND()

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

Это примерно в 2,5 раза быстрее , чем подход в принятом ответе .

Предупреждение : Это медленно для больших наборов данных с миллионами записей, поэтому вы можете добавить предложение limit.

128 голосов
/ 02 мая 2010

Я не нашел идеального способа сделать это, по крайней мере, без двух запросов.

Следующее использует случайно сгенерированное число (до текущего количества записей) в качестве смещения .

offset = rand(Model.count)

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

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

Если честно, я только что использовал ORDER BY RAND () или RANDOM () (в зависимости от базы данных). Это не проблема с производительностью, если у вас нет проблем с производительностью.

73 голосов
/ 02 мая 2010

Ваш пример кода начнет вести себя неточно после удаления записи (это будет несправедливо отдавать предпочтение элементам с более низким идентификатором)

Возможно, вам лучше использовать случайные методы в вашей базе данных. Они различаются в зависимости от того, какую БД вы используете, но: order => «RAND ()» работает для mysql, а: order => «RANDOM ()» работает для postgres

Model.first(:order => "RANDOM()") # postgres example
28 голосов
/ 10 мая 2011

Сравнительный анализ этих двух методов в MySQL 5.1.49, Ruby 1.9.2p180 для таблицы продуктов с + 5 миллионами записей:

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 выглядит намного медленнее.

EDIT Я тоже пробовал

Product.first(:order => "RAND()")

Но мне пришлось убить его через ~ 60 секунд. MySQL был "Копирование в таблицу tmp на диске". Это не сработает.

18 голосов
/ 19 августа 2013

Это не должно быть так сложно.

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

pluck возвращает массив всех идентификаторов в таблице. Метод sample в массиве возвращает случайный идентификатор из массива.

Это должно работать хорошо, с равной вероятностью выбора и поддержкой таблиц с удаленными строками. Вы можете даже смешать это с ограничениями.

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

И тем самым выберите случайного пользователя, который любит пятницы, а не просто любого пользователя.

13 голосов
/ 17 августа 2011

Я сделал рельсы для 3 камней, чтобы справиться с этим:

https://github.com/spilliton/randumb

Это позволяет вам делать такие вещи:

Model.where(:column => "value").random(10)
10 голосов
/ 31 мая 2012

Не рекомендуется использовать это решение, но если по какой-то причине вы действительно хотите выбрать случайным образом запись при выполнении только одного запроса к базе данных, вы можете использовать метод sample из Класс Ruby Array , который позволяет выбрать случайный элемент из массива.

Model.all.sample

Этот метод требует только запроса к базе данных, но он значительно медленнее, чем альтернативы, такие как Model.offset(rand(Model.count)).first, которые требуют два запроса к базе данных, хотя последний все еще предпочтителен.

8 голосов
/ 11 ноября 2013

Я так часто использую это из консоли, расширяю ActiveRecord в инициализаторе - пример Rails 4:

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

Затем я могу позвонить Foo.random, чтобы вернуть случайную запись.

5 голосов
/ 30 ноября 2012

Один запрос в 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)
5 голосов
/ 08 ноября 2017

Чтение всего этого не давало мне уверенности в том, что из них лучше всего подойдет в моей конкретной ситуации с 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)

Этот ответ объединяет, проверяет и обновляет Ответ Мохамеда , а также комментарий Нами ВАНГ к тому же и комментарий Флориана Пилза относительно принятого ответа - пожалуйста, отправьте им голоса!

...