ActiveRecordを介してテーブルからランダムなレコードを取得する必要があります。 2006年のJamis Buck の例に従いました。
ただし、Google検索を介して別の方法に出くわしました(新しいユーザーの制限のため、リンクに関連付けることはできません)。
Rand_id = Rand(Model.count)
Rand_record = Model.first(:conditions => ["id >= ?", Rand_id])
私はここの他の人がそれをどのようにしたのか、または誰がどの方法がより効率的であるかを知っているなら興味があります。
少なくとも2つのクエリがなければ、これを行う理想的な方法は見つかりませんでした。
以下では、ランダムに生成された数(現在のレコード数まで)をoffsetとして使用します。
offset = Rand(Model.count)
# Rails 4
Rand_record = Model.offset(offset).first
# Rails 3
Rand_record = Model.first(:offset => offset)
正直に言うと、ORDER BY Rand()またはRANDOM()(データベースに応じて)を使用しています。パフォーマンスの問題がなければ、パフォーマンスの問題ではありません。
サンプルコードは、レコードが削除されると不正確に動作し始めます(低いIDのアイテムが不当に優先されます)。
おそらく、データベース内でランダムな方法を使用する方が良いでしょう。これらは使用しているDBによって異なりますが、mysqlでは:order => "Rand()"が機能し、postgresでは:order => "RANDOM()"が機能します
Model.first(:order => "RANDOM()") # postgres example
MySQL 5.1.49でのこれら2つのメソッドのベンチマーク、Ruby 1.9.2p180、+ 500万レコードの製品テーブル:
def random1
Rand_id = Rand(Product.count)
Rand_record = Product.first(:conditions => [ "id >= ?", Rand_id])
end
def random2
if (c = Product.count) != 0
Product.find(:first, :offset =>Rand(c))
end
end
n = 10
Benchmark.bm(7) do |x|
x.report("next id:") { n.times {|i| random1 } }
x.report("offset:") { n.times {|i| random2 } }
end
user system total real
next id: 0.040000 0.000000 0.040000 ( 0.225149)
offset : 0.020000 0.000000 0.020000 ( 35.234383)
MySQLのオフセットははるかに遅いようです。
EDIT私も試しました
Product.first(:order => "Rand()")
しかし、私は約60秒後にそれを殺さなければなりませんでした。 MySQLは「ディスク上のtmpテーブルにコピー」していました。それはうまくいきません。
そんなに難しいことはありません。
ids = Model.pluck(:id)
random_model = Model.find(ids.sample)
pluck
は、テーブル内のすべてのIDの配列を返します。配列のsample
メソッドは、配列からランダムIDを返します。
これは、選択の確率が等しく、削除された行のあるテーブルをサポートして、うまく機能するはずです。制約と組み合わせることもできます。
User.where(favorite_day: "Friday").pluck(:id)
そして、それによって、どんなユーザーよりも金曜日が好きなランダムなユーザーを選びます。
これを処理するRails 3 gemを作成しました。
https://github.com/spilliton/randumb
次のようなことができます:
Model.where(:column => "value").random(10)
このソリューションを使用することはお勧めしませんが、何らかの理由でreallyデータベースクエリを1つだけ作成しながらレコードをランダムに選択したい場合は、 Ruby配列クラス のsample
メソッドを使用できます。これにより、配列からランダムなアイテムを選択できます。
Model.all.sample
この方法はデータベースクエリのみを必要としますが、2つのデータベースクエリを必要とするModel.offset(Rand(Model.count)).first
のような代替方法よりもかなり遅いですが、後者が依然として好まれます。
私はこれをコンソールから頻繁に使用し、初期化子でActiveRecordを拡張します-Rails 4の例:
class ActiveRecord::Base
def self.random
self.limit(1).offset(Rand(self.count)).first
end
end
その後、Foo.random
を呼び出して、ランダムなレコードを戻すことができます。
Postgresの1つのクエリ:
User.order('RANDOM()').limit(3).to_sql # Postgres example
=> "SELECT "users".* FROM "users" ORDER BY RANDOM() LIMIT 3"
オフセットを使用して、2つのクエリ:
offset = Rand(User.count) # returns an integer between 0 and (User.count - 1)
Model.offset(offset).limit(1)
これらのすべてを読んでも、Rails 5およびMySQL/Maria 5.5を使用して、これらのどれが私の特定の状況で最適に機能するかについて、多くの自信を得ることができませんでした。そこで、65000件のレコードでいくつかの回答をテストし、2つのテイクアウェイを用意しました。
limit
は明確な勝者です。pluck
+ sample
を使用しないでください。def random1
Model.find(Rand((Model.last.id + 1)))
end
def random2
Model.order("Rand()").limit(1)
end
def random3
Model.pluck(:id).sample
end
n = 100
Benchmark.bm(7) do |x|
x.report("find:") { n.times {|i| random1 } }
x.report("order:") { n.times {|i| random2 } }
x.report("pluck:") { n.times {|i| random3 } }
end
user system total real
find: 0.090000 0.000000 0.090000 ( 0.127585)
order: 0.000000 0.000000 0.000000 ( 0.002095)
pluck: 6.150000 0.000000 6.150000 ( 8.292074)
この回答は、合成、検証、更新 Mohamedの回答 、および同じことに対するWANGのコメントと受け入れられた回答に関するFlorian Pilzのコメント-それらに投票を送ってください!
Array
メソッドsample
を使用できます。メソッドsample
は配列からランダムなオブジェクトを返します。使用するには、単純なActiveRecord
クエリで実行するだけです。コレクションを返す、例えば:
User.all.sample
このようなものを返します:
#<User id: 25, name: "John Doe", email: "[email protected]", created_at: "2018-04-16 19:31:12", updated_at: "2018-04-16 19:31:12">
ランダムレコードにこのgemを強くお勧めします。これは、大量のデータ行を持つテーブル用に特別に設計されています。
https://github.com/haopingfan/quick_random_records
このgemを除く他のすべての回答は、大規模なデータベースではパフォーマンスが低下します。
4.6ms
のみです。User.order('Rand()').limit(10)
コスト733.0ms
。offset
アプローチコスト245.4ms
完全に。User.all.sample(10)
アプローチコスト573.4ms
。注:私のテーブルには120,000人のユーザーしかいません。レコードが多いほど、パフォーマンスの差は大きくなります。
指定されたスコープ内のランダムな結果を選択する必要がある場合:
scope :male_names, -> { where(sex: 'm') }
number_of_results = 10
Rand = Names.male_names.pluck(:id).sample(number_of_results)
Names.where(id: Rand)
非常に多くの回答を見た後、すべてをPostgreSQL(9.6.3)データベースでベンチマークすることにしました。より小さな100,000テーブルを使用し、Model.order( "RANDOM()")。firstを削除しました。これは、すでに2桁遅いためです。
10列の2,500,000エントリのテーブルを使用すると、勝者の勝者は勝者メソッドのほぼ8倍の速さで勝ちました(オフセット。ローカルサーバーでのみ実行しました。また、これは問題を引き起こす可能性があることに注意する価値があります。これらはそれぞれ一意ではないので、一度に複数の結果を取り出すことができます。
Pluckは25,000,000行のテーブルで100回の実行に勝ちます。編集:実際、この時間にはループのpluckが含まれます。しかしながら;かなりの量のRAMを消費します。
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)
ランダムを除外するために、100,000行のテーブルで2000回実行されるデータを次に示します。
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)
MySQLデータベースの場合:Model.order( "Rand()")。first
リストからアイテムをランダムに選択する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 }
これは、モデルのサイズが既にキャッシュされている場合は1つのクエリ、それ以外の場合は2つのクエリになります。
Rails 4.2およびOracle:
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
PostgreSQL 9.5以降を使用している場合は、 TABLESAMPLE
を利用してランダムレコードを選択できます。
2つのデフォルトのサンプリング方法(SYSTEM
およびBERNOULLI
)では、返される行の数を、テーブル内の行の総数に対する割合として指定する必要があります。
-- Fetch 10% of the rows in the customers table.
SELECT * FROM customers TABLESAMPLE BERNOULLI(10);
これには、適切な割合を選択するためにテーブル内のレコードの量を知る必要がありますが、すぐに見つけるのは容易ではありません。幸いなことに、 tsm_system_rows
module があり、直接返す行数を指定できます。
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
サンプリングメソッドが完全にランダムになるのか、それともランダムページから最初の行を返すだけなのかはわかりません。
この情報のほとんどは、 Gulcin Yildirimによる2ndQuadrantブログ投稿 から取得されました。
Rails 4.2.8のBenchmarkを使用して、アプリでSamの例を試してみますRecordNotFound: 'id' = 0))のカテゴリが見つかりませんでした。
def random1
2.4.1 :071?> Category.find(Rand(1..Category.count))
2.4.1 :072?> end
=> :random1
2.4.1 :073 > def random2
2.4.1 :074?> Category.offset(Rand(1..Category.count))
2.4.1 :075?> end
=> :random2
2.4.1 :076 > def random3
2.4.1 :077?> Category.offset(Rand(1..Category.count)).limit(Rand(1..3))
2.4.1 :078?> end
=> :random3
2.4.1 :079 > def random4
2.4.1 :080?> Category.pluck(Rand(1..Category.count))
2.4.1 :081?>
2.4.1 :082 > end
=> :random4
2.4.1 :083 > n = 100
=> 100
2.4.1 :084 > Benchmark.bm(7) do |x|
2.4.1 :085 > x.report("find") { n.times {|i| random1 } }
2.4.1 :086?> x.report("offset") { n.times {|i| random2 } }
2.4.1 :087?> x.report("offset_limit") { n.times {|i| random3 } }
2.4.1 :088?> x.report("pluck") { n.times {|i| random4 } }
2.4.1 :089?> end
user system total real
find 0.070000 0.010000 0.080000 (0.118553)
offset 0.040000 0.010000 0.050000 (0.059276)
offset_limit 0.050000 0.000000 0.050000 (0.060849)
pluck 0.070000 0.020000 0.090000 (0.099065)
RANDOM()
の使用に加えて、これをスコープにスローすることもできます。
class Thing
scope :random, -> (limit = 1) {
order('RANDOM()').
limit(limit)
}
end
または、スコープとしてそれを使いたくない場合は、クラスメソッドにスローします。現在、Thing.random
はThing.random(n)
とともに機能します。
どうするか:
Rand_record = Model.find(Model.pluck(:id).sample)
私にとっては非常に明確です
非常に古い質問ですが、
Rand_record = Model.all.shuffle
レコードの配列を取得し、ランダムな順序で並べ替えます。宝石やスクリプトは必要ありません。
1つのレコードが必要な場合:
Rand_record = Model.all.shuffle.first
私はRoRを初めて使いましたが、これは私のために働きました:
def random
@cards = Card.all.sort_by { Rand }
end
から来ました:
.order('RANDOM()').limit(limit)
はきれいに見えますが、limit
が1であっても(Railsではなくデータベースで内部的に)すべての行をフェッチおよびソートする必要があるため、大きなテーブルでは低速です。 MySQLについてはわかりませんが、これはPostgresで起こります。 here および here の詳細説明。
大きなテーブルの1つのソリューションは、.from("products TABLESAMPLE SYSTEM(0.5)")
です。ここで、0.5
は0.5%
を意味します。ただし、多くの行を除外するWHERE
条件がある場合、この解決策は依然として低速です。 WHERE
条件が適用される前にTABLESAMPLE SYSTEM(0.5)
すべての行をフェッチするためだと思います。
大きなテーブルの別の解決策は(非常にランダムではありません)です:
products_scope.limit(sample_size).sample(limit)
sample_size
には100
を指定できます(ただし、大きすぎない場合は低速で大量のメモリを消費します)。limit
には1
を指定できます。これは高速ですが、実際にはランダムではありませんが、sample_size
レコード内でのみランダムであることに注意してください。
PS:2回目に実行される一部のDBクエリは、DBキャッシュのおかげで1回目に実行するよりも大幅に高速になる可能性があるため、上記の回答のベンチマーク結果は(少なくともPostgresでは)信頼できません。残念ながら、これらのベンチマークを信頼できるものにするために、Postgresでキャッシュを無効にする簡単な方法はありません。