Rails 3: получить случайную запись

Итак, я нашел несколько примеров для поиска случайной записи в Rails 2 - предпочтительный метод выглядит следующим образом:

Thing.find :first, :offset => rand(Thing.count)

Будучи чем-то новичком, я не уверен, как это можно построить с помощью нового синтаксиса find в Rails 3.

Итак, что такое "Rails 3 Way", чтобы найти случайную запись?

Ответ 1

Thing.first(:order => "RANDOM()") # For MySQL :order => "RAND()", - thanx, @DanSingerman
# Rails 3
Thing.order("RANDOM()").first

или

Thing.first(:offset => rand(Thing.count))
# Rails 3
Thing.offset(rand(Thing.count)).first

Собственно, в Rails 3 будут работать все примеры. Но использование порядка RANDOM довольно медленное для больших таблиц, но больше sql-style

UPD. Вы можете использовать следующий трюк в индексированном столбце (синтаксис PostgreSQL):

select * 
from my_table 
where id >= trunc(
  random() * (select max(id) from my_table) + 1
) 
order by id 
limit 1;

Ответ 2

Я работаю над проектом (Rails 3.0.15, ruby ​​1.9.3-p125-perf), где db находится в localhost, а таблица пользователей имеет бит более записей 100K.

Используя

по RAND()

довольно медленный

User.order( "RAND (идентификатор)" ). Первая

становится

SELECT users. * FROM users ORDER BY RAND (id) LIMIT 1

и требуется от 8 до 12 секунд.

Журнал рельсов:

Пользовательская нагрузка (11030.8ms) SELECT users. * FROM users ORDER BY RAND() LIMIT 1

из mysql explain

+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows   | Extra                           |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
|  1 | SIMPLE      | users | ALL  | NULL          | NULL | NULL    | NULL | 110165 | Using temporary; Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+

Вы можете видеть, что индекс не используется (possible_keys = NULL), создается временная таблица и требуется дополнительный проход для получения желаемого значения ( extra = Использование временного; FileSort).

С другой стороны, разделяя запрос на две части и используя Ruby, мы имеем разумное улучшение времени отклика.

users = User.scoped.select(:id);nil
User.find( users.first( Random.rand( users.length )).last )

(; nil для использования в консоли)

Журнал рельсов:

Пользовательская загрузка (25,2 мс) SELECT id FROM users Пользовательская нагрузка (0.2ms) SELECT users. * FROM users ГДЕ users. id= 106854 LIMIT 1

и mysql объясняют, почему:

+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| id | select_type | table | type  | possible_keys | key                      | key_len | ref  | rows   | Extra       |
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
|  1 | SIMPLE      | users | index | NULL          | index_users_on_user_type | 2       | NULL | 110165 | Using index |
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+

+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | users | const | PRIMARY       | PRIMARY | 4       | const |    1 |       |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+

теперь мы можем использовать только индексы и первичный ключ и выполнять работу примерно в 500 раз быстрее!

UPDATE:

как указано icantbecool в комментариях, вышеупомянутое решение имеет недостаток, если в таблице удалены записи.

Обходной путь в этом случае может быть

users_count = User.count
User.scoped.limit(1).offset(rand(users_count)).first

который переводит на два запроса

SELECT COUNT(*) FROM `users`
SELECT `users`.* FROM `users` LIMIT 1 OFFSET 148794

и работает примерно в 500 мс.

Ответ 3

Я сделал рельсы 3 жемчужины для этого, что лучше работает на больших таблицах и позволяет цепочки отношений и областей:

https://github.com/spilliton/randumb

(edit): По умолчанию мой жемчуг по умолчанию использует тот же подход, что и выше, но вы можете использовать старый путь, если хотите:)

Ответ 4

При использовании Postgres

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

Если вы используете MySQL

User.limit(5).order("RAND()")

В обоих случаях вы произвольно выбираете 5 записей из таблицы Users. Вот фактический запрос SQL, отображаемый в консоли.

SELECT * FROM users ORDER BY RANDOM() LIMIT 5

Ответ 5

здесь мы идем

рельсы

#in your initializer
module ActiveRecord
  class Base
    def self.random
      if (c = count) != 0
        find(:first, :offset =>rand(c))
      end
    end
  end
end

Использование

Model.random #returns single random object

или вторая мысль

module ActiveRecord
  class Base
    def self.random
      order("RAND()")
    end
  end
end

использование:

Model.random #returns shuffled collection

Ответ 6

Многие из опубликованных ответов фактически не будут хорошо работать на довольно больших таблицах (1 + миллион строк). Случайное упорядочение быстро занимает несколько секунд, и делать счет на столе также занимает довольно много времени.

Решение, которое хорошо работает для меня в этой ситуации, заключается в использовании RANDOM() с условием where:

Thing.where('RANDOM() >= 0.9').take

В таблице с более чем миллионом строк этот запрос обычно занимает менее 2 мс.

Ответ 7

Это было очень полезно для меня, однако мне нужно было немного больше гибкости, так вот что я сделал:

Случай1: поиск одной случайной записи источник: trevor turk site
Добавьте это в модель Thing.rb

def self.random
    ids = connection.select_all("SELECT id FROM things")
    find(ids[rand(ids.length)]["id"].to_i) unless ids.blank?
end

то в вашем контроллере вы можете вызвать что-то вроде этого

@thing = Thing.random

Случай 2: поиск нескольких случайных записей (без повторов) источник: не могу вспомнить
Мне нужно было найти 10 случайных записей без повторов, поэтому я нашел работу
В контроллере:

thing_ids = Thing.find( :all, :select => 'id' ).map( &:id )
@things = Thing.find( (1..10).map { thing_ids.delete_at( thing_ids.size * rand ) } )

Это найдет 10 случайных записей, однако стоит упомянуть, что если база данных будет особенно большой (миллионы записей), это не будет идеальным, и производительность будет затруднена. Я буду исполнять до нескольких тысяч записей, которые были достаточными для меня.

Ответ 8

Метод Ruby для случайного выбора элемента из списка sample. Желая создать эффективный sample для ActiveRecord и на основе предыдущих ответов, я использовал:

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 }

Ответ 9

Работает в Rails 5 и является агностиком DB:

Это в вашем контроллере:

@quotes = Quote.offset(rand(Quote.count - 3)).limit(3)

Вы можете, конечно, поставить это в беспокойство, как показано здесь.

приложение/модели/проблемы/randomable.rb

module Randomable
  extend ActiveSupport::Concern

  class_methods do
    def random(the_count = 1)
      records = offset(rand(count - the_count)).limit(the_count)
      the_count == 1 ? records.first : records
    end
  end
end

то...

приложение/модели/book.rb

class Book < ActiveRecord::Base
  include Randomable
end

Затем вы можете просто использовать:

Books.random

или

Books.random(3)

Ответ 11

Если используется Oracle

User.limit(10).order("DBMS_RANDOM.VALUE")

Выход

SELECT * FROM users ORDER BY DBMS_RANDOM.VALUE WHERE ROWNUM <= 10

Ответ 12

Очень простой способ получить несколько случайных записей из таблицы. Это делает 2 дешевых запроса.

Model.where(id: Model.pluck(:id).sample(3))

Вы можете изменить "3" на количество случайных записей, которые вы хотите.

Ответ 13

Я просто столкнулся с этой проблемой при разработке небольшого приложения, где я хотел выбрать случайный вопрос из моей БД. Я использовал:

@question1 = Question.where(:lesson_id => params[:lesson_id]).shuffle[1]

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