it-swarm.com.ru

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

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

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

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

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

131
Andrew
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;
214
fl00r

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

С помощью 

заказ Рэнд ()

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

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

становится

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

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

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

Пользовательская нагрузка (11030,8 мс) SELECT users. * FROM users ORDER BY 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 ), создается временная таблица и требуется дополнительный проход для получения желаемого значения ( дополнительный = Использование временного; Использование сортировки файлов ).

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

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

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

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

Пользовательская нагрузка (25,2 мс) SELECT id FROM users Пользовательская загрузка (0,2 мс) SELECT users. * FROM users WHERE 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 раз быстрее!

Обновление:

как указано в комментариях 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 мс.

29
xlembouras

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

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

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

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

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

SELECT * FROM users ORDER BY RANDOM() LIMIT 5
12
icantbecool

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

https://github.com/spilliton/randumb

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

11
spilliton

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

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

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

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

6
fivedigit

вот так

Рельсы путь

#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
5
huan son

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

Случай 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

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

4
Hishalv

Метод 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
dankohn

Работает в 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)
3
richardun

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

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

Результат

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

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

Например.

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

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

1
Trond

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

https://github.com/haopingfan/quick_random_records

Все остальные ответы плохо работают с большой базой данных, кроме этого гема: 

  1. quick_random_records только стоит 4.6ms полностью.

 enter image description here

  1. принятый ответ User.order('Rand()').limit(10) стоимость 733.0ms.

 enter image description here

  1. offset подход стоит 245.4ms полностью.

 enter image description here

  1. User.all.sample(10) стоимость подхода 573.4ms.

 enter image description here

Примечание. В моей таблице всего 120 000 пользователей. Чем больше у вас записей, тем больше будет разница в производительности.


Обновление: 

Выполните на таблице с 550 000 строк

  1. Model.where(id: Model.pluck(:id).sample(10)) стоимость 1384.0ms

 enter image description here

  1. gem: quick_random_records только стоимость 6.4ms полностью

 enter image description here

0
Derek Fan