ActiveRecord의 무작위 레코드
ActiveRecord를 통해 테이블에서 임의의 레코드를 가져와야합니다. 2006 년 Jamis Buck 의 예제를 따랐습니다 .
그러나 Google 검색을 통해 다른 방법으로 접근했습니다 (새로운 사용자 제한으로 인해 링크로 속성을 지정할 수 없음).
rand_id = rand(Model.count)
rand_record = Model.first(:conditions => ["id >= ?", rand_id])
나는 다른 사람들이 어떻게 그것을했는지 또는 누군가가 어떤 방법이 더 효율적인지 알고 있는지 궁금합니다.
적어도 두 번의 쿼리 없이이 작업을 수행하는 이상적인 방법을 찾지 못했습니다.
다음은 임의로 생성 된 숫자 (현재 레코드 수까지)를 오프셋으로 사용 합니다.
offset = rand(Model.count)
# Rails 4
rand_record = Model.offset(offset).first
# Rails 3
rand_record = Model.first(:offset => offset)
솔직히 말하면 방금 데이터베이스에 따라 ORDER BY RAND () 또는 RANDOM ()을 사용하고 있습니다. 성능 문제가없는 경우 성능 문제가 아닙니다.
에서는 레일 (4) 및 (5) 사용 PostgreSQL을 또는 SQLite는 사용 RANDOM()
:
Model.order('RANDOM()').first
을 위해 일하는 것이 아마도 같은 MySQL을 로RAND()
Model.order('RAND()').first
이것은 허용 된 답변 의 접근 방식보다 약 2.5 배 빠릅니다 .
주의 사항 : 수백만 개의 레코드가있는 대용량 데이터 세트의 경우 속도가 느리므로 limit
절 을 추가 할 수 있습니다 .
레코드가 삭제되면 예제 코드가 부정확하게 작동하기 시작합니다 (ID가 낮은 항목은 부당하게 선호됩니다)
데이터베이스 내에서 임의의 방법을 사용하는 것이 좋습니다. 이것은 사용중인 DB에 따라 다르지만 : order => "RAND ()"는 mysql에서 작동하고 : order => "RANDOM ()"은 postgres에서 작동합니다.
Model.first(:order => "RANDOM()") # postgres example
+5.1 만 레코드가있는 제품 테이블에서 MySQL 5.1.49, Ruby 1.9.2p180에서이 두 가지 방법을 벤치마킹하십시오.
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의 오프셋은 훨씬 느려 보입니다.
편집 도 시도
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)
따라서 모든 사용자가 아닌 금요일을 좋아하는 임의의 사용자를 선택하십시오.
나는 이것을 처리하기 위해 레일 3 보석을 만들었습니다.
https://github.com/spilliton/randumb
다음과 같은 작업을 수행 할 수 있습니다.
Model.where(:column => "value").random(10)
당신이이 솔루션을 사용하는 것이 어떤 이유로 당신이 경우에, 그러나 것이 좋습니다되지 정말 하나의 데이터베이스 쿼리를하는 동안 무작위로 레코드를 선택하려면, 당신은 사용할 수 있습니다 sample
으로부터 방법을 루비 Array 클래스 는 임의의 항목을 선택할 수 있습니다, 배열에서.
Model.all.sample
이 방법은 데이터베이스 쿼리 만 필요하지만 Model.offset(rand(Model.count)).first
두 데이터베이스 쿼리가 필요한 대안보다 훨씬 느리지 만 후자는 여전히 선호됩니다.
콘솔에서 자주 사용합니다. 초기화-Rails 4 예제에서 ActiveRecord를 확장합니다.
class ActiveRecord::Base
def self.random
self.limit(1).offset(rand(self.count)).first
end
end
그런 다음 Foo.random
무작위 레코드를 다시 불러 오기 위해 전화 를 걸 수 있습니다 .
Postgres에서 하나의 쿼리 :
User.order('RANDOM()').limit(3).to_sql # Postgres example
=> "SELECT "users".* FROM "users" ORDER BY RANDOM() LIMIT 3"
오프셋을 사용하여 두 개의 쿼리 :
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 레코드에 대한 답변 중 일부를 테스트했으며 두 가지를 제거했습니다.
- RAND ()
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의 답변 과 동일한 답변에 대한 Nami WANG의 의견 및 수락 된 답변에 대한 Florian Pilz의 의견을 종합, 검증 및 업데이트 합니다.
당신이 사용할 수있는 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">
지정된 범위 내에서 임의의 결과 를 선택해야하는 경우 :
scope :male_names, -> { where(sex: 'm') }
number_of_results = 10
rand = Names.male_names.pluck(:id).sample(number_of_results)
Names.where(id: rand)
많은 데이터 행이있는 테이블을 위해 특별히 설계된 임의 레코드에 대해이 gem을 강력하게 권장합니다.
https://github.com/haopingfan/quick_random_records
이 gem을 제외하고 다른 모든 답변은 큰 데이터베이스에서 제대로 수행되지 않습니다.
- quick_random_records는
4.6ms
총 비용 입니다.
User.order('RAND()').limit(10)
비용733.0ms
.
- 허용 된 답변
offset
접근 비용은245.4ms
총 비용 입니다.
User.all.sample(10)
접근 비용573.4ms
.
Note: My table only has 120,000 users. The more records you have, the more enormous the difference of performance will be.
The Ruby method for randomly picking an item from a list is sample
. Wanting to create an efficient sample
for ActiveRecord, and based on the previous answers, I used:
module ActiveRecord
class Base
def self.sample
offset(rand(size)).first
end
end
end
I put this in lib/ext/sample.rb
and then load it with this in config/initializers/monkey_patches.rb
:
Dir[Rails.root.join('lib/ext/*.rb')].each { |file| require file }
This will be one query if the size of the model is already cached and two otherwise.
Rails 4.2 and Oracle:
For oracle you can set a scope on your Model like so:
scope :random_order, -> {order('DBMS_RANDOM.RANDOM')}
or
scope :random_order, -> {order('DBMS_RANDOM.VALUE')}
And then for a sample call it like this:
Model.random_order.take(10)
or
Model.random_order.limit(5)
of course you could also place an order without a scope like so:
Model.all.order('DBMS_RANDOM.RANDOM') # or DBMS_RANDOM.VALUE respectively
For MySQL database try: Model.order("RAND()").first
If you're using PostgreSQL 9.5+, you can take advantage of TABLESAMPLE
to select a random record.
The two default sampling methods (SYSTEM
and BERNOULLI
) require that you specify the number of rows to return as a percentage of the total number of rows in the table.
-- Fetch 10% of the rows in the customers table.
SELECT * FROM customers TABLESAMPLE BERNOULLI(10);
This requires knowing the amount of records in the table to select the appropriate percentage, which may not be easy to find quickly. Fortunately, there is the tsm_system_rows
module that allows you to specify the number of rows to return directly.
CREATE EXTENSION tsm_system_rows;
-- Fetch a single row from the customers table.
SELECT * FROM customers TABLESAMPLE SYSTEM_ROWS(1);
To use this within ActiveRecord, first enable the extension within a migration:
class EnableTsmSystemRowsExtension < ActiveRecord::Migration[5.0]
def change
enable_extension "tsm_system_rows"
end
end
Then modify the from
clause of the query:
customer = Customer.from("customers TABLESAMPLE SYSTEM_ROWS(1)").first
I don't know if the SYSTEM_ROWS
sampling method will be entirely random or if it just returns the first row from a random page.
Most of this information was taken from a 2ndQuadrant blog post written by Gulcin Yildirim.
After seeing so many answers I decided to benchmark them all on my PostgreSQL(9.6.3) database. I use a smaller 100,000 table and got rid of the Model.order("RANDOM()").first since it was already two orders of magnitude slower.
Using a table with 2,500,000 entries with 10 columns the hands down winner was the pluck method being almost 8 times faster than the runner up(offset. I only ran this on a local server so that number might be inflated but its bigger enough that the pluck method is what I'll end up using. It's also worth noting that this might cause issues is you pluck more than 1 result at a time since each one of those will be unique aka less random.
Pluck wins running 100 time on my 25,000,000 row table Edit: actually this time includes the pluck in the loop if I take it out it it runs about as fast as simple iteration on the id. However; it does take up a fair amount of 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)
Here is the data running 2000 times on my 100,000 row table to rule out random
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)
I'm brand new to RoR but I got this to work for me:
def random
@cards = Card.all.sort_by { rand }
end
It came from:
How to randomly sort (scramble) an array in Ruby?
What about to do:
rand_record = Model.find(Model.pluck(:id).sample)
For me is much clear
I try this of Sam's example on my App using rails 4.2.8 of Benchmark( I put 1..Category.count for random, because if the random takes a 0 it will produce an error(ActiveRecord::RecordNotFound: Couldn't find Category with 'id'=0)) and the mine was:
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)
.order('RANDOM()').limit(limit)
looks neat but is slow for large tables because it needs to fetch and sort all rows even if limit
is 1 (internally in database but not in Rails). I'm not sure about MySQL but this happens in Postgres. More explanation in here and here.
One solution for large tables is .from("products TABLESAMPLE SYSTEM(0.5)")
where 0.5
means 0.5%
. However, I find this solution is still slow if you have WHERE
conditions that filter out a lot of rows. I guess it's because TABLESAMPLE SYSTEM(0.5)
fetch all rows before WHERE
conditions apply.
Another solution for large tables (but not very random) is:
products_scope.limit(sample_size).sample(limit)
where sample_size
can be 100
(but not too large otherwise it's slow and consumes a lot of memory), and limit
can be 1
. Note that although this is fast but it's not really random, it's random within sample_size
records only.
PS: Benchmark results in answers above are not reliable (at least in Postgres) because some DB queries running at 2nd time can be significantly faster than running at 1st time, thanks to DB cache. And unfortunately there is no easy way to disable cache in Postgres to make these benchmarks reliable.
Very old question but with :
rand_record = Model.all.shuffle
You got an Array of record, sort by random order. No need gems or scripts.
If you want one record :
rand_record = Model.all.shuffle.first
Along with using RANDOM()
, you can also throw this into a scope:
class Thing
scope :random, -> (limit = 1) {
order('RANDOM()').
limit(limit)
}
end
또는 그것을 범위로 표현하지 않으면 클래스 메소드에 던져 넣으십시오. 이제 Thing.random
와 함께 작동합니다 Thing.random(n)
.
참고 URL : https://stackoverflow.com/questions/2752231/random-record-in-activerecord
'Programing' 카테고리의 다른 글
문자열에서 여러 문자를 바꾸는 가장 좋은 방법은 무엇입니까? (0) | 2020.06.13 |
---|---|
Emacs에서 전체 라인을 어떻게 복제합니까? (0) | 2020.06.13 |
반복되는 문자로 채워진 가변 길이의 문자열을 만듭니다. (0) | 2020.06.13 |
matplotlib에서 서브 플롯에 대해 xlim 및 ylim을 설정하는 방법 (0) | 2020.06.12 |
SQL Server 보안 주체 "dbo"가 없습니다. (0) | 2020.06.12 |