Rails 3: Nhận bản ghi ngẫu nhiên


132

Vì vậy, tôi đã tìm thấy một số ví dụ để tìm bản ghi ngẫu nhiên trong Rails 2 - phương pháp ưa thích dường như là:

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

Là một cái gì đó của một người mới Tôi không chắc làm thế nào điều này có thể được xây dựng bằng cú pháp tìm kiếm mới trong Rails 3.

Vì vậy, những gì "Rails 3 Way" để tìm một bản ghi ngẫu nhiên?



9
^^ ngoại trừ tôi đặc biệt tìm kiếm Rails 3 cách tối ưu, đó là toàn bộ mục đích của câu hỏi.
Andrew

rails 3 cụ thể chỉ là chuỗi truy vấn :)
fl00r 17/03/2016

Câu trả lời:


216
Thing.first(:order => "RANDOM()") # For MySQL :order => "RAND()", - thanx, @DanSingerman
# Rails 3
Thing.order("RANDOM()").first

hoặc là

Thing.first(:offset => rand(Thing.count))
# Rails 3
Thing.offset(rand(Thing.count)).first

Trên thực tế, trong Rails 3 tất cả các ví dụ sẽ hoạt động. Nhưng sử dụng thứ tự RANDOMkhá chậm đối với các bảng lớn nhưng kiểu sql hơn

CẬP NHẬT. Bạn có thể sử dụng thủ thuật sau đây trên một cột được lập chỉ mục (cú pháp PostgreSQL):

select * 
from my_table 
where id >= trunc(
  random() * (select max(id) from my_table) + 1
) 
order by id 
limit 1;

11
Ví dụ đầu tiên của bạn sẽ không làm việc trong MySQL mặc dù - cú pháp cho MySQL là Thing.first (: trật tự => "RAND ()") (a nguy cơ viết SQL thay vì sử dụng trừu tượng ActiveRecord)
DanSingerman

@ DanSingerman, vâng, đó là DB cụ thể RAND()hoặc RANDOM(). Cảm ơn
fl00r 17/03/2016

Và điều này sẽ không tạo ra vấn đề nếu có mục bị thiếu trong chỉ mục? (nếu một cái gì đó ở giữa ngăn xếp bị xóa, liệu có khả năng nó sẽ được yêu cầu không?
Victor S

@VictorS, không, nó sẽ không #offset chỉ đi đến bản ghi có sẵn tiếp theo. Tôi đã thử nghiệm nó với Ruby 1.9.2 và Rails 3.1
SooDesuNe

1
@JohnMerlino, có 0 là bù, không phải id. Offet 0 có nghĩa là mục đầu tiên theo thứ tự.
fl00r

29

Tôi đang làm việc trên một dự án ( Rails 3.0.15, ruby ​​1.9.3-p125-perf ) trong đó db nằm trong localhost và bảng người dùng có hơn 100 nghìn bản ghi .

Sử dụng

đặt hàng bởi RAND ()

khá chậm

User.order ("RAND (id)"). Đầu tiên

trở thành

CHỌN users. * TỪ usersĐẶT HÀNG THEO RAND (id) GIỚI HẠN 1

và mất từ 8 đến 12 giây để trả lời !!

Nhật ký đường ray:

Tải người dùng (11030.8ms) CHỌN users. * TỪ usersĐẶT HÀNG B RNG RAND () GIỚI HẠN 1

từ lời giải thích của 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 |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+

Bạn có thể thấy rằng không có chỉ mục nào được sử dụng ( ossible_keys = NULL ), một bảng tạm thời được tạo và yêu cầu thêm một lượt để tìm nạp giá trị mong muốn ( thêm = Sử dụng tạm thời; Sử dụng tệp ).

Mặt khác, bằng cách chia truy vấn thành hai phần và sử dụng Ruby, chúng tôi có sự cải thiện hợp lý về thời gian phản hồi.

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

(; không cho sử dụng bàn điều khiển)

Nhật ký đường ray:

Tải người dùng (25,2ms) CHỌN id TỪ usersTải người dùng (0,2ms) CHỌN users. * TỪ usersĐÂU users. id= 106854 GIỚI HẠN 1

và lời giải thích của mysql chứng minh tại sao:

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

bây giờ chúng ta chỉ có thể sử dụng các chỉ mục và khóa chính và thực hiện công việc nhanh hơn khoảng 500 lần!

CẬP NHẬT:

như được chỉ ra bởi icantbecool trong các bình luận, giải pháp trên có một lỗ hổng nếu có các bản ghi bị xóa trong bảng.

Một cách giải quyết trong đó có thể là

users_count = User.count
User.scoped.limit(1).offset(rand(users_count)).first

có nghĩa là hai truy vấn

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

và chạy trong khoảng 500ms.


thêm ".id" sau "lần cuối" vào ví dụ thứ hai của bạn sẽ tránh được lỗi "không thể tìm thấy Mô hình không có ID". Ví dụ: User.find (users.first (Random.rand (users.length)). Last.id)
turing_machine

Cảnh báo! Trong MySQL RAND(id)sẽ KHÔNG cung cấp cho bạn một thứ tự ngẫu nhiên khác nhau cho mỗi truy vấn. Sử dụng RAND()nếu muốn một thứ tự khác nhau mỗi truy vấn.
Justin Tanner

User.find (users.first (Random.rand (users.length)). Last.id) sẽ không hoạt động nếu có một bản ghi bị xóa. [1,2,4,5,] và nó có khả năng có thể chọn id là 3, nhưng sẽ không có mối quan hệ hồ sơ hoạt động.
icantbecool

Ngoài ra, users = User.scoped.select (: id); nil không được dùng nữa. Sử dụng cái này thay thế: users = User.where (nil) .select (: id)
icantbecool

Tôi tin rằng việc sử dụng Random.rand (users.length) làm tham số đầu tiên là một lỗi. Random.rand có thể trả về 0. Khi 0 được sử dụng làm tham số đầu tiên, giới hạn được đặt thành 0 và điều này không trả về bản ghi nào. Những gì người ta nên sử dụng thay vì là 1 + Ngẫu nhiên (users.length) giả sử người dùng.length> 0.
SWoo

12

Nếu sử dụng Postgres

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

Nếu sử dụng MySQL

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

Trong cả hai trường hợp, bạn chọn ngẫu nhiên 5 bản ghi từ bảng Người dùng. Đây là truy vấn SQL thực tế được hiển thị trong bảng điều khiển.

SELECT * FROM users ORDER BY RANDOM() LIMIT 5

11

Tôi đã tạo ra một viên ngọc 3 rails để làm điều này hoạt động tốt hơn trên các bảng lớn và cho phép bạn xâu chuỗi các mối quan hệ và phạm vi:

https://github.com/spilliton/randumb

(chỉnh sửa): Về cơ bản, hành vi mặc định của đá quý của tôi về cơ bản sử dụng cách tiếp cận như trên, nhưng bạn có tùy chọn sử dụng cách cũ nếu bạn muốn :)


6

Nhiều câu trả lời được đăng thực sự sẽ không hoạt động tốt trên các bảng khá lớn (hơn 1 triệu hàng). Đặt hàng ngẫu nhiên nhanh chóng mất vài giây và việc đếm trên bàn cũng mất khá nhiều thời gian.

Một giải pháp phù hợp với tôi trong tình huống này là sử dụng RANDOM()với điều kiện:

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

Trên một bảng có hơn một triệu hàng, truy vấn này thường mất ít hơn 2ms.


Một ưu điểm khác của giải pháp của bạn là sử dụng takehàm cung cấp LIMIT(1)truy vấn nhưng trả về phần tử đơn thay vì mảng. Vì vậy, chúng tôi không cần phải gọifirst
Piotr Galas

Dường như với tôi rằng các hồ sơ ở đầu bảng có khả năng nuôi ong cao hơn được chọn theo cách này, có thể không phải là những gì bạn muốn đạt được.
Gorn

5

chúng ta đi đây

đường ray

#in your initializer
module ActiveRecord
  class Base
    def self.random
      if (c = count) != 0
        find(:first, :offset =>rand(c))
      end
    end
  end
end

sử dụng

Model.random #returns single random object

hoặc ý nghĩ thứ hai là

module ActiveRecord
  class Base
    def self.random
      order("RAND()")
    end
  end
end

sử dụng:

Model.random #returns shuffled collection

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

nếu không có bất kỳ người dùng nào và bạn muốn có 2, thì bạn sẽ gặp lỗi. có lý.
Tim Kretschmer

1
Cách tiếp cận thứ hai sẽ không hoạt động với postgres, nhưng "RANDOM()"thay vào đó bạn có thể sử dụng ...
Daniel Richter

4

Điều này rất hữu ích với tôi tuy nhiên tôi cần linh hoạt hơn một chút, vì vậy đây là những gì tôi đã làm:

Case1: Tìm một nguồn bản ghi ngẫu nhiên : trang web
trevor turk Thêm cái này vào mô hình 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

sau đó trong bộ điều khiển của bạn, bạn có thể gọi một cái gì đó như thế này

@thing = Thing.random

Trường hợp 2: Tìm nhiều bản ghi ngẫu nhiên (không lặp lại) nguồn: không thể nhớ
tôi cần tìm 10 bản ghi ngẫu nhiên không lặp lại vì vậy đây là những gì tôi thấy có hiệu quả
trong bộ điều khiển của bạn:

thing_ids = Thing.find( :all, :select => 'id' ).map( &:id )
@things = Thing.find( (1..10).map { thing_ids.delete_at( thing_ids.size * rand ) } )

Điều này sẽ tìm thấy 10 bản ghi ngẫu nhiên, tuy nhiên điều đáng nói là nếu cơ sở dữ liệu đặc biệt lớn (hàng triệu bản ghi), điều này sẽ không lý tưởng và hiệu suất sẽ bị cản trở. Là sẽ thực hiện tốt lên đến một vài ngàn hồ sơ đó là đủ cho tôi.


4

Phương pháp Ruby để chọn ngẫu nhiên một mục từ danh sách là sample. Muốn tạo hiệu quả samplecho ActiveRecord và dựa trên các câu trả lời trước đó, tôi đã sử dụng:

module ActiveRecord
  class Base
    def self.sample
      offset(rand(size)).first
    end
  end
end

Tôi đặt cái này vào lib/ext/sample.rbvà sau đó tải nó vào config/initializers/monkey_patches.rb:

Dir[Rails.root.join('lib/ext/*.rb')].each { |file| require file }

Trên thực tế, #countsẽ thực hiện một cuộc gọi đến DB cho a COUNT. Nếu bản ghi đã được tải, đây có thể là một ý tưởng tồi. Một công cụ tái cấu trúc sẽ được sử dụng #sizethay vì nó sẽ quyết định xem có #countnên sử dụng hay không, nếu bản ghi đã được tải, sẽ sử dụng #length.
BenMorganIO

Chuyển từ countđể sizedựa trên phản hồi của bạn. Thêm thông tin tại: dev.mensfeld.pl/2014/09/ khăn
Dan Kohn

3

Hoạt động trong Rails 5 và là thuyết bất khả tri của DB:

Điều này trong bộ điều khiển của bạn:

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

Tất nhiên, bạn có thể đặt điều này trong một mối quan tâm như được hiển thị ở đây .

ứng dụng / mô hình / mối quan tâm / ngẫu nhiên.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

sau đó...

ứng dụng / mô hình / book.rb

class Book < ActiveRecord::Base
  include Randomable
end

Sau đó, bạn có thể sử dụng đơn giản bằng cách làm:

Books.random

hoặc là

Books.random(3)

Điều này luôn lấy các bản ghi tiếp theo, ít nhất cần phải được ghi lại (vì nó có thể không phải là những gì người dùng muốn).
Gorn

2

Bạn có thể sử dụng mẫu () trong ActiveRecord

Ví dụ

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

Nguồn: http://thinkingeek.com/2011/07/04/easily-select-random-records-rails/


33
Đây là một truy vấn rất tệ để sử dụng nếu bạn có số lượng lớn các bản ghi, vì DB sẽ chọn TẤT CẢ các bản ghi, sau đó Rails sẽ chọn năm bản ghi từ đó - gây lãng phí ồ ạt.
DaveStephens

5
samplekhông có trong ActiveRecord, mẫu nằm trong Mảng. api.rubyonrails.org/groupes/Array.html#method-i-sample
Frans

3
Đây là một cách đắt tiền để có được một bản ghi ngẫu nhiên, đặc biệt là từ một bảng lớn. Rails sẽ tải một đối tượng cho mọi bản ghi từ bảng của bạn vào bộ nhớ. Nếu bạn cần bằng chứng, hãy chạy 'rails console', hãy thử 'someModelFromYourApp.find (: all) .sample (5)' và xem SQL được tạo ra.
Eliot Sykes

1
Xem câu trả lời của tôi, biến câu trả lời đắt giá này thành một vẻ đẹp hợp lý để có được nhiều hồ sơ ngẫu nhiên.
Arcolye

1

Nếu sử dụng Oracle

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

Đầu ra

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

1

Rất khuyến nghị loại đá quý này cho các bản ghi ngẫu nhiên, được thiết kế đặc biệt cho bảng có nhiều hàng dữ liệu:

https://github.com/haopingfan/quick_random_records

Tất cả các câu trả lời khác thực hiện không tốt với cơ sở dữ liệu lớn, ngoại trừ viên ngọc này:

  1. quick_random_records chỉ có giá 4.6mshoàn toàn.

nhập mô tả hình ảnh ở đây

  1. User.order('RAND()').limit(10)chi phí trả lời được chấp nhận 733.0ms.

nhập mô tả hình ảnh ở đây

  1. các offsetphương pháp tiếp cận chi phí 245.4mshoàn toàn.

nhập mô tả hình ảnh ở đây

  1. các User.all.sample(10)chi phí tiếp cận 573.4ms.

nhập mô tả hình ảnh ở đây

Lưu ý: Bảng của tôi chỉ có 120.000 người dùng. Bạn càng có nhiều hồ sơ, sự khác biệt về hiệu suất sẽ càng lớn.


CẬP NHẬT:

Thực hiện trên bàn với 550.000 hàng

  1. Model.where(id: Model.pluck(:id).sample(10)) Giá cả 1384.0ms

nhập mô tả hình ảnh ở đây

  1. gem: quick_random_recordschỉ chi phí 6.4mshoàn toàn

nhập mô tả hình ảnh ở đây


-2

Một cách rất dễ dàng để có được nhiều bản ghi ngẫu nhiên từ bảng. Điều này làm cho 2 truy vấn giá rẻ.

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

Bạn có thể thay đổi "3" thành số lượng bản ghi ngẫu nhiên bạn muốn.


1
không, phần Model.pluck (: id) .sample (3) không rẻ. Nó sẽ đọc trường id cho mọi phần tử trong bảng.
Maximiliano Guzman

Có cách nào cơ sở dữ liệu nhanh hơn?
Arcolye

-5

Tôi vừa gặp vấn đề này khi phát triển một ứng dụng nhỏ nơi tôi muốn chọn một câu hỏi ngẫu nhiên từ DB của mình. Tôi đã sử dụng:

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

Và nó hoạt động tốt với tôi. Tôi không thể nói về hiệu suất của các DB lớn hơn vì đây chỉ là một ứng dụng nhỏ.


Vâng, đây chỉ là nhận tất cả các hồ sơ của bạn và sử dụng các phương pháp mảng ruby ​​trên chúng. Hạn chế tất nhiên là nó có nghĩa là tải tất cả các bản ghi của bạn vào bộ nhớ, sau đó sắp xếp lại chúng một cách ngẫu nhiên, sau đó lấy mục thứ hai trong mảng sắp xếp lại. Đó chắc chắn có thể là một con heo nhớ nếu bạn đang làm việc với một bộ dữ liệu lớn. Bỏ qua một bên, tại sao không lấy yếu tố đầu tiên? (tức là. shuffle[0])
Andrew

phải xáo trộn [0]
Marcelo Áo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.