web-dev-qa-db-ja.com

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)dbがlocalhostとusersテーブルにあります100Kレコードより少し多い。

を使用して

rand()による注文

かなり遅い

User.order( "Rand(id)")。first

になる

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

8から12秒で応答します!!

Railsログ:

ユーザー負荷(11030.8ms)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 |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+

インデックスが使用されていないことがわかります(possible_keys = NULL)、一時テーブルが作成され、目的の値を取得するには追加のパスが必要です(extra = Using temporary; Using filesort)。

一方、クエリを2つの部分に分割し、Rubyを使用することで、応答時間を合理的に改善できます。

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

(;コンソール使用の場合はなし)

Railsログ:

ユーザー負荷(25.2ms)SELECT id FROM usersユーザー負荷(0.2ms)SELECT users。* FROM users WHERE users..id = 106854 LIMIT 1

mysqlのExplainはその理由を証明します:

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

これは2つのクエリに変換されます

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

約500msで実行されます。

29
xlembouras

Postgresを使用している場合

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

MySQLを使用している場合

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

どちらの場合も、ユーザーテーブルから5つのレコードをランダムに選択しています。以下は、コンソールに表示される実際のSQLクエリです。

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

これを行うためにRails 3 gemを作成しました。これは、大きなテーブルでパフォーマンスが向上し、リレーションとスコープを連鎖させることができます。

https://github.com/spilliton/randumb

(編集):私のgemのデフォルトの動作は、基本的に上記と同じアプローチを使用していますが、必要に応じて古い方法を使用するオプションがあります:)

11
spilliton

実際に投稿された回答の多くは、かなり大きなテーブル(100万行以上)ではうまく機能しません。ランダムな順序付けにはすぐに数秒かかり、テーブルでのカウントも非常に長くかかります。

この状況でうまく機能する解決策は、RANDOM()をwhere条件で使用することです:

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

100万を超える行があるテーブルでは、このクエリの所要時間は通常2ミリ秒未満です。

6
fivedigit

さあ

Railsの方法

#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:ランダムなレコードを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個見つかりますが、データベースが特に大きい場合(数百万のレコード)、これは理想的ではなく、パフォーマンスが低下することに注意してください。私にとっては十分な数千件のレコードまで実行できます。

4
Hishalv

リストからアイテムをランダムに選択するRubyメソッドはsampleです。 ActiveRecordの効率的なsampleを作成したいので、以前の回答に基づいて、次を使用しました。

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
Dan Kohn

Rails 5で動作し、DBに依存しません:

これはあなたのコントローラーで:

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

もちろん、これを here のように懸念事項に入れることができます。

app/models/concerns/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

その後...

app/models/book.rb

class Book < ActiveRecord::Base
  include Randomable
end

次に、次の操作を行うだけで使用できます。

Books.random

または

Books.random(3)
3
richardun

ActiveRecordでsample()を使用できます

例えば。

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

ソース: http://thinkingeek.com/2011/07/04/easily-select-random-records-Rails/

2
Trond

ランダムレコードにこのgemを強くお勧めします。これは、大量のデータ行を持つテーブル用に特別に設計されています。

https://github.com/haopingfan/quick_random_records

このgemを除く他のすべての回答は、大規模なデータベースではパフォーマンスが低下します。

  1. quick_random_recordsのコストは4.6msのみです。

enter image description here

  1. 受け入れられた回答User.order('Rand()').limit(10) cost 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

1
Derek Fan

Oracleを使用している場合

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

出力

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