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

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

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

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

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

Ответы [ 24 ]

3 голосов
/ 17 апреля 2018

Вы можете использовать Array метод sample, метод sample возвращает случайный объект из массива, для его использования вам просто нужно выполнить простой запрос ActiveRecord, который возвращает коллекцию, например:

User.all.sample

вернет что-то вроде этого:

#<User id: 25, name: "John Doe", email: "admin@example.info", created_at: "2018-04-16 19:31:12", updated_at: "2018-04-16 19:31:12">
2 голосов
/ 05 февраля 2015

Если вам нужно выбрать некоторые случайные результаты в указанной области действия :

scope :male_names, -> { where(sex: 'm') }
number_of_results = 10

rand = Names.male_names.pluck(:id).sample(number_of_results)
Names.where(id: rand)
1 голос
/ 23 сентября 2017

Увидев так много ответов, я решил сравнить их все в моей базе данных PostgreSQL (9.6.3). Я использую меньшую таблицу из 100 000 и сначала избавился от Model.order ("RANDOM ()"). Так как он был уже на два порядка медленнее.

Используя таблицу с 2 500 000 записей с 10 столбцами, победителем хендсайда был метод срывания, который был почти в 8 раз быстрее, чем занявший второе место (смещение. Я запускал его только на локальном сервере, так что число может быть завышено, но его достаточно больше то, что я в конечном итоге буду использовать метод срывания, также стоит отметить, что это может вызвать проблемы, если вы отбираете более 1 результата за раз, так как каждый из них будет уникальным или менее случайным.

Pluck выигрывает 100 раз за мой стол с 25 000 000 строк. Редактировать: на самом деле на этот раз включает в себя срыв в цикле, если я вычеркну его, он работает примерно так же быстро, как простая итерация по идентификатору. Тем не мение; он занимает довольно много оперативной памяти.

RandomModel                 user     system      total        real
Model.find_by(id: i)       0.050000   0.010000   0.060000 (  0.059878)
Model.offset(rand(offset)) 0.030000   0.000000   0.030000 ( 55.282410)
Model.find(ids.sample)     6.450000   0.050000   6.500000 (  7.902458)

Вот данные, запущенные 2000 раз в моей таблице 100 000 строк, чтобы исключить случайные

RandomModel       user     system      total        real
find_by:iterate  0.010000   0.000000   0.010000 (  0.006973)
offset           0.000000   0.000000   0.000000 (  0.132614)
"RANDOM()"       0.000000   0.000000   0.000000 ( 24.645371)
pluck            0.110000   0.020000   0.130000 (  0.175932)
1 голос
/ 06 ноября 2016

Если вы используете PostgreSQL 9.5+, вы можете воспользоваться TABLESAMPLE для выбора случайной записи.

Два метода выборки по умолчанию (SYSTEM и BERNOULLI) требуют, чтобы вы указали количество возвращаемых строк в процентах от общего числа строк в таблице.

-- Fetch 10% of the rows in the customers table.
SELECT * FROM customers TABLESAMPLE BERNOULLI(10);

Это требует знания количества записей в таблице, чтобы выбрать соответствующий процент, который может быть нелегко найти быстро. К счастью, есть модуль tsm_system_rows , который позволяет вам указать количество строк, которые будут возвращаться напрямую.

CREATE EXTENSION tsm_system_rows;

-- Fetch a single row from the customers table.
SELECT * FROM customers TABLESAMPLE SYSTEM_ROWS(1);

Чтобы использовать это в ActiveRecord, сначала включите расширение в миграции:

class EnableTsmSystemRowsExtension < ActiveRecord::Migration[5.0]
  def change
    enable_extension "tsm_system_rows"
  end
end

Затем измените предложение from запроса:

customer = Customer.from("customers TABLESAMPLE SYSTEM_ROWS(1)").first

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

Большая часть этой информации взята из блога 2ndQuadrant, написанного Gulcin Yildirim .

1 голос
/ 07 января 2016

Rails 4.2 и Oracle :

Для оракула вы можете установить область действия вашей модели следующим образом:

scope :random_order, -> {order('DBMS_RANDOM.RANDOM')}

или

scope :random_order, -> {order('DBMS_RANDOM.VALUE')}

А затем для примера вызовите это так:

Model.random_order.take(10)

или

Model.random_order.limit(5)

Конечно, вы также можете разместить заказ без объема:

Model.all.order('DBMS_RANDOM.RANDOM') # or DBMS_RANDOM.VALUE respectively
1 голос
/ 30 августа 2015

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

Это будет один запрос, если размер модели уже кэширован, и два в противном случае.

1 голос
/ 05 декабря 2015

Для базы данных MySQL попробуйте: Model.order ("RAND ()"). First

1 голос
/ 18 мая 2018

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

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 пользователей. Чем больше у вас записей, тем больше будет разница в производительности.

0 голосов
/ 11 февраля 2019

Наряду с использованием RANDOM() вы также можете добавить это в область действия:

class Thing
  scope :random, -> (limit = 1) {
    order('RANDOM()').
    limit(limit)
  }
end

Или, если вам это не нравится, просто добавьте его в метод класса. Теперь Thing.random работает вместе с Thing.random(n).

0 голосов
/ 17 января 2019

Очень старый вопрос, но с:

rand_record = Model.all.shuffle

Вы получили массив записей, отсортированный по случайному порядку. Не нужно драгоценных камней или сценариев.

Если вы хотите одну запись:

rand_record = Model.all.shuffle.first
...