Rails 3: получить случайную запись - PullRequest
131 голосов
/ 17 марта 2011

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

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

Будучи новичком, я не уверен, как это можно построить, используя новый синтаксис поиска в Rails 3.

Итак, что такое "Rails 3 Way" для поиска случайной записи?

Ответы [ 14 ]

214 голосов
/ 17 марта 2011
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

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

select * 
from my_table 
where id >= trunc(
  random() * (select max(id) from my_table) + 1
) 
order by id 
limit 1;
29 голосов
/ 11 июля 2012

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

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

заказ по RAND ()

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

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

становится

ВЫБРАТЬ users. * ОТ users ЗАКАЗАТЬ ПО RAND (id) LIMIT 1

и занимает от 8 до 12 секунд , чтобы ответить !!

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

Пользовательская нагрузка (11030,8 мс) SELECT users. * ОТ users ЗАКАЗАТЬ ПО RAND () LIMIT 1

из объяснения MySQL

+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
| 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 |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+

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

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

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

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

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

Пользовательская нагрузка (25,2 мс) SELECT id ОТ users Пользовательская нагрузка (0,2 мс) SELECT users. * ОТ users ГДЕ users. id = 106854 ПРЕДЕЛ 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 мс.

12 голосов
/ 02 июня 2015

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

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

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

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

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

SELECT * FROM users ORDER BY RANDOM() LIMIT 5
11 голосов
/ 17 августа 2011

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

https://github.com/spilliton/randumb

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

6 голосов
/ 19 сентября 2014

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

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

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

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

5 голосов
/ 12 апреля 2013

здесь мы идем

путь рельсов

#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
4 голосов
/ 02 ноября 2014

Метод 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 }
4 голосов
/ 13 декабря 2011

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

Случай 1: Поиск одной случайной записи источник: сайт Тревора ТуркаДобавьте это к модели 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

Case2: поиск нескольких случайных записей (без повторов) источник: не помню Мне нужно было найти 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 случайных записей, однако стоит отметить, что если база данных особенно велика (миллионы записей), это не будет идеально, и производительность будет снижена.Будет хорошо работать до нескольких тысяч записей, что было достаточно для меня.

3 голосов
/ 16 декабря 2016

Работает в Rails 5 и не зависит от БД:

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

@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)
2 голосов
/ 20 августа 2012

Вы можете использовать sample () в ActiveRecord

* 1003 Е.Г. *

def get_random_things_for_home_page
  find(:all).sample(5)
end

Источник: http://thinkingeek.com/2011/07/04/easily-select-random-records-rails/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...