Rails 3 : 랜덤 레코드 받기


132

따라서 Rails 2에서 무작위 레코드를 찾는 몇 가지 예를 찾았습니다. 선호하는 방법은 다음과 같습니다.

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

초보자의 무언가이기 때문에 Rails 3의 새로운 찾기 구문을 사용하여 어떻게 구성 할 수 있는지 잘 모르겠습니다.

랜덤 레코드를 찾는 "Rails 3 Way"는 무엇입니까?



9
^^ 특별히 Rails 3의 최적의 방법을 찾고 있습니다.
Andrew

rails 3 specific은 쿼리 체인에만 해당됩니다. :)
fl00r

답변:


216
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;

11
첫 번째 예제는 MySQL에서 작동하지 않습니다. MySQL의 구문은 Thing.first (: order => "RAND ()")입니다 (ActiveRecord 추상화를 사용하지 않고 SQL을 작성하는 위험)
DanSingerman

@ DanSingerman, 예, 특정 DB RAND()또는 RANDOM()입니다. 감사합니다
fl00r

색인에서 누락 된 항목이 있으면 문제가되지 않습니까? (스택 중간에 무언가 삭제되면 요청 될 가능성이 있습니까?
Victor S

@VictorS, 아니 #offset되지 않습니다 다음 사용 가능한 레코드로 이동합니다. Ruby 1.9.2와 Rails 3.1
SooDesuNe

1
@JohnMerlino, 예 0은 id가 아니라 오프셋입니다. Offet 0은 주문에 따른 첫 번째 항목을 의미합니다.
fl00r

29

DB가 localhost에 있고 users 테이블에 100K 개 이상의 레코드 가있는 프로젝트 ( Rails 3.0.15, ruby ​​1.9.3-p125-perf )를 작업 중입니다. 입니다.

사용

RAND ()로 주문

꽤 느리다

User.order ( "RAND (id)"). first

된다

RAND (ID) 제한으로 주문 users. *에서 선택 users1

로부터 얻어 812 초응답 에서 !!

레일스 로그 :

사용자로드 (11030.8ms) 선택 users. * usersRAND () 제한 순서 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 = 임시 사용; 파일 정렬 사용 ).

반면 쿼리를 두 부분으로 나누고 Ruby를 사용하면 응답 시간이 상당히 향상됩니다.

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

(; 콘솔 사용에는 없음)

레일스 로그 :

사용자로드 (25.2ms) SELECT id FROM users사용자로드 (0.2ms) SELECT users. * FROM usersWHERE users. id= 106854 제한 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

약 500ms에서 실행됩니다.


두 번째 예제에 "last"뒤에 ".id"를 추가하면 "ID가없는 모델을 찾을 수 없습니다"오류가 발생하지 않습니다. 예 : User.find (users.first (Random.rand (users.length)). last.id)
turing_machine

경고! 에서 MySQL은 RAND(id)NOT 다른 임의의 순서로 당신에게 모든 쿼리를 제공합니다. RAND()쿼리마다 다른 순서를 원하면 사용하십시오 .
Justin Tanner

삭제 된 레코드가 있으면 User.find (users.first (Random.rand (users.length)). last.id)가 작동하지 않습니다. [1,2,4,5,] 그리고 잠재적으로 ID 3을 선택할 수 있지만 활성 레코드 관계는 없습니다.
icantbecool 2016 년

또한 users = User.scoped.select (: id); nil은 사용되지 않습니다. (ID) 사용자 = User.where (전무) ALL 기타 사항 서보 -OFF :이 대신 사용
icantbecool

나는 Random.rand (users.length)를 매개 변수로 사용하는 것이 버그라고 생각합니다. Random.rand는 0을 반환 할 수 있습니다. 0을 매개 변수로 처음 사용하는 경우 제한은 0으로 설정되며 레코드가 반환되지 않습니다. 대신에 사용해야하는 것은 users.length> 0이라고 가정하고 1 + 랜덤 (users.length)입니다.
SWoo

12

Postgres를 사용하는 경우

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

MySQL을 사용하는 경우

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

두 경우 모두 Users 테이블에서 무작위로 5 개의 레코드를 선택합니다. 다음은 콘솔에 표시되는 실제 SQL 쿼리입니다.

SELECT * FROM users ORDER BY RANDOM() LIMIT 5

11

큰 테이블에서 더 잘 수행하고 관계와 범위를 연결할 수 있도록 레일 3 gem을 만들었습니다.

https://github.com/spilliton/randumb

(편집) : 내 보석의 기본 동작은 기본적으로 위와 동일한 접근법을 사용하지만 원하는 경우 이전 방식을 사용할 수 있습니다. :)


6

게시 된 많은 답변이 실제로 큰 테이블 (1 + 백만 행)에서 제대로 수행되지 않습니다. 임의 순서는 빠르게 몇 초가 걸리고 테이블에서 계산을 수행하는 데에도 시간이 오래 걸립니다.

이 상황에서 나에게 잘 맞는 해결책 RANDOM()은 where 조건과 함께 사용 하는 것입니다.

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

백만 개가 넘는 행이있는 테이블에서이 쿼리는 일반적으로 2ms 미만이 소요됩니다.


솔루션의 또 다른 장점 takeLIMIT(1)쿼리 를 제공 하지만 배열 대신 단일 요소를 반환 하는 함수를 사용 한다는 것입니다. 따라서 우리는 호출 할 필요가 없습니다first
Piotr Galas

테이블 시작 부분의 레코드는이 방법으로 선택한 확률이 높기 때문에 달성하려는 것이 아닐 수도 있습니다.
갔다

5

여기 우리는 간다

레일스 웨이

#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

Couldn't find all Users with 'id': (first, {:offset=>1}) (found 0 results, but was looking for 2)
Bruno

사용자가없고 2를 받고 싶다면 오류가 발생합니다. 이해합니다.
Tim Kretschmer

1
두 번째 방법은 postgres에서 작동하지 않지만 "RANDOM()"대신 사용할 수 있습니다 .
Daniel Richter

4

이것은 나에게 매우 유용했지만 좀 더 융통성이 필요했기 때문에 이것이 내가 한 일입니다.

사례 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

목록에서 항목을 임의로 선택하는 Ruby 방법은 sample입니다. sampleActiveRecord를 효율적으로 만들고 싶었고 이전 답변을 바탕으로 다음을 사용했습니다.

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 }

실제로, #count에 대한 DB 호출을 수행합니다 COUNT. 레코드가 이미로드 된 경우 이는 나쁜 생각 일 수 있습니다. 리 팩터는 #size대신 사용 #count해야하는지 또는 레코드가 이미로드되어 있는지를 결정하기 때문에 대신 사용 하는 것 #length입니다.
BenMorganIO

의견 countsize바탕으로 에서 (으) 로 전환했습니다 . 자세한 정보 : dev.mensfeld.pl/2014/09/…
Dan Kohn

3

Rails 5에서 작동하며 DB에 독립적입니다.

이것은 컨트롤러에서 :

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

물론 여기에 표시된대로이를 우려 할 수 있습니다 .

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

그때...

앱 / 모델 /book.rb

class Book < ActiveRecord::Base
  include Randomable
end

그런 다음 다음을 수행하여 간단히 사용할 수 있습니다.

Books.random

또는

Books.random(3)

이것은 항상 후속 레코드를 가져 오며, 최소한 사용자가 원하는 것이 아닐 수 있으므로 문서화해야합니다.

2

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/


33
DB가 모든 레코드를 선택하고 Rails가 그 중에서 5 개의 레코드를 선택하기 때문에 많은 양의 레코드가있는 경우 사용하기 매우 좋지 않은 쿼리입니다.
DaveStephens

5
sampleActiveRecord에없고 샘플이 배열에 있습니다. api.rubyonrails.org/classes/Array.html#method-i-sample
Frans

3
이것은 특히 큰 테이블에서 임의의 레코드를 가져 오는 비싼 방법입니다. Rails는 모든 레코드의 객체를 테이블에서 메모리로로드합니다. 증명이 필요한 경우 'rails console'을 실행하고 'SomeModelFromYourApp.find (: all) .sample (5)'를 시도하고 생성 된 SQL을보십시오.
Eliot Sykes

1
내 답변을 참조하십시오.이 비싼 답변은 여러 개의 무작위 레코드를 얻는 간소화 된 아름다움으로 바뀝니다.
Arcolye

1

Oracle을 사용하는 경우

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

산출

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

1

많은 데이터 행이있는 테이블을 위해 특별히 설계된 임의 레코드에 대해이 gem을 강력하게 권장합니다.

https://github.com/haopingfan/quick_random_records

이 gem을 제외하고 다른 모든 답변은 큰 데이터베이스에서 제대로 수행되지 않습니다.

  1. quick_random_records는 4.6ms총 비용 입니다.

여기에 이미지 설명을 입력하십시오

  1. 허용 된 답변 User.order('RAND()').limit(10)비용 733.0ms.

여기에 이미지 설명을 입력하십시오

  1. offset접근 방식은 비용이 245.4ms완전히.

여기에 이미지 설명을 입력하십시오

  1. User.all.sample(10)접근 비용 573.4ms.

여기에 이미지 설명을 입력하십시오

참고 : 내 테이블에는 120,000 명의 사용자 만 있습니다. 기록이 많을수록 성능 차이가 더 커집니다.


최신 정보:

550,000 개의 행이있는 테이블에서 수행

  1. Model.where(id: Model.pluck(:id).sample(10)) 비용 1384.0ms

여기에 이미지 설명을 입력하십시오

  1. gem: quick_random_records6.4ms완전히 비용

여기에 이미지 설명을 입력하십시오


-2

테이블에서 여러 개의 무작위 레코드를 얻는 매우 쉬운 방법입니다. 이렇게하면 2 개의 저렴한 쿼리가 만들어집니다.

Model.where(id: Model.pluck(:id).sample(3))

"3"을 원하는 임의의 레코드 수로 변경할 수 있습니다.


1
아니요. Model.pluck (: id) .sample (3) 부분은 저렴하지 않습니다. 테이블의 모든 요소에 대한 id 필드를 읽습니다.
Maximiliano Guzman

더 빠른 데이터베이스 독립적 방법이 있습니까?
Arcolye

-5

방금 DB에서 무작위 질문을 선택하려는 작은 응용 프로그램을 개발하는이 문제에 부딪 쳤습니다. 나는 사용했다 :

@question1 = Question.where(:lesson_id => params[:lesson_id]).shuffle[1]

그리고 그것은 나를 위해 잘 작동합니다. 더 큰 DB의 성능이 작은 응용 프로그램이므로 어떻게 큰 DB의 성능에 대해 말할 수 없습니다.


예, 이것은 모든 레코드를 가져오고 루비 배열 메소드를 사용하는 것입니다. 단점은 물론 모든 레코드를 메모리에로드 한 다음 무작위로 재정렬 한 다음 다시 정렬 된 배열에서 두 번째 항목을 가져 오는 것을 의미합니다. 큰 데이터 세트를 다루는 경우 분명히 메모리 호그가 될 수 있습니다. 미성년자, 왜 첫 번째 요소를 잡아? (예. shuffle[0])
앤드류

셔플해야합니다 [0]
Marcelo Austria
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.