Bản ghi ngẫu nhiên trong ActiveRecord


151

Tôi đang cần nhận một bản ghi ngẫu nhiên từ một bảng thông qua ActiveRecord. Tôi đã theo dõi ví dụ từ Jamis Buck từ năm 2006 .

Tuy nhiên, tôi cũng đã tìm thấy một cách khác thông qua một tìm kiếm Google (không thể thuộc tính với một liên kết do hạn chế người dùng mới):

 rand_id = rand(Model.count)
 rand_record = Model.first(:conditions => ["id >= ?", rand_id])

Tôi tò mò làm thế nào những người khác ở đây đã làm điều đó hoặc nếu có ai biết cách nào sẽ hiệu quả hơn.


2
2 điểm có thể giúp trả lời. 1. Id của bạn được phân phối đồng đều như thế nào, chúng có tuần tự không? 2. Làm thế nào ngẫu nhiên nó cần phải được? Đủ tốt ngẫu nhiên, hay ngẫu nhiên thực sự?
Michael

Chúng là các id tuần tự được tự động tạo bởi Activerecord và nó chỉ cần đủ tốt.
jyunderwood

1
Sau đó, giải pháp đề xuất của bạn gần với lý tưởng :) Tôi sẽ sử dụng "CHỌN MAX (id) TỪ tên_bảng" thay vì COUNT (*) vì nó sẽ xử lý các hàng bị xóa tốt hơn một chút, nếu không, phần còn lại vẫn ổn. Nói tóm lại, nếu "đủ tốt" là ổn, thì bạn chỉ cần có một phương pháp giả định phân phối gần với những gì bạn thực sự có. Nếu nó đồng nhất và ngay cả như bạn đã nói, rand đơn giản hoạt động rất tốt.
Michael

1
Điều này sẽ không hoạt động khi bạn đã xóa hàng.
Venkat D.

Câu trả lời:


136

Tôi đã không tìm thấy một cách lý tưởng để làm điều này mà không có ít nhất hai truy vấn.

Sau đây sử dụng một số được tạo ngẫu nhiên (tối đa số lượng bản ghi hiện tại) làm phần .

offset = rand(Model.count)

# Rails 4
rand_record = Model.offset(offset).first

# Rails 3
rand_record = Model.first(:offset => offset)

Thành thật mà nói, tôi vừa sử dụng ORDER BY RAND () hoặc RANDOM () (tùy thuộc vào cơ sở dữ liệu). Đây không phải là vấn đề về hiệu suất nếu bạn không gặp vấn đề về hiệu suất.


2
Model.find(:offset => offset).firstsẽ ném lỗi. Tôi nghĩ rằng Model.first(:offset => offset)có thể thực hiện tốt hơn.
Harish Shetty

1
vâng, tôi đã làm việc với Rails 3 và tiếp tục bối rối về các định dạng truy vấn giữa các phiên bản.
Toby Hede

7
Lưu ý rằng việc sử dụng offset rất chậm với tập dữ liệu lớn, vì nó thực sự cần quét chỉ mục (hoặc quét bảng, trong trường hợp chỉ mục được nhóm được sử dụng như InnoDB). Nói cách khác, đó là hoạt động O (N) nhưng "WHERE id> = # {rand_id} ĐẶT HÀNG theo id ASC LIMIT 1" là O (log N), nhanh hơn nhiều.
kenn

15
Xin lưu ý rằng phương pháp bù trừ chỉ mang lại một điểm dữ liệu được tìm thấy ngẫu nhiên (điểm đầu tiên, tất cả sau đó vẫn được sắp xếp theo id). Nếu bạn cần nhiều bản ghi được chọn ngẫu nhiên, bạn phải sử dụng phương pháp này nhiều lần hoặc sử dụng phương thức đặt hàng ngẫu nhiên do cơ sở dữ liệu của bạn cung cấp, tức là Thing.order("RANDOM()").limit(100)cho 100 mục được chọn ngẫu nhiên. (Xin lưu ý rằng nó có RANDOM()trong PostgreSQL và RAND()trong MySQL ... không thể mang theo như bạn có thể muốn.)
Florian Pilz

3
Không hoạt động với tôi trên Rails 4. Sử dụng Model.offset(offset).first.
mahemoff

206

Đường ray 6

Như Jason đã nêu trong các bình luận, trong Rails 6, các đối số không thuộc tính không được phép. Bạn phải bọc giá trị trong một Arel.sql()tuyên bố.

Model.order(Arel.sql('RANDOM()')).first

Đường ray 5, 4

Trong Rails 45 , sử dụng Postgresql hoặc SQLite , sử dụng RANDOM():

Model.order('RANDOM()').first

Có lẽ điều tương tự sẽ làm việc với MySQL vớiRAND()

Model.order('RAND()').first

Điều này nhanh hơn khoảng 2,5 lần so với cách tiếp cận trong câu trả lời được chấp nhận .

Hãy cẩn thận : Điều này chậm đối với các bộ dữ liệu lớn với hàng triệu bản ghi, vì vậy bạn có thể muốn thêm một limitmệnh đề.


4
"Random ()" cũng hoạt động trong sqlite, vì vậy đối với những người trong chúng ta vẫn đang phát triển trên sqlite và chạy postgres trong sản xuất, giải pháp của bạn hoạt động trong cả hai môi trường.
wuliwong

5
Tôi đã tạo một điểm chuẩn cho điều này so với câu trả lời được chấp nhận. Trên Postgresql 9.4, cách tiếp cận của câu trả lời này nhanh gấp khoảng hai lần.
panmari

3
Có vẻ như nó không được khuyến nghị trên mysql webtrenches.com/post.cfm/avoid-rand-in-mysql
Prakash Murthy

Đây là giải pháp nhanh nhất
Sergio Belevskij

1
"Các đối số không thuộc tính sẽ không được phép trong Rails 6.0. Phương thức này không được gọi với các giá trị do người dùng cung cấp, chẳng hạn như tham số yêu cầu hoặc thuộc tính mô hình. Các giá trị an toàn đã biết có thể được chuyển qua bằng cách gói chúng trong Arel.sql ()."
Trenton Tyler

73

Mã ví dụ của bạn sẽ bắt đầu hoạt động không chính xác sau khi các bản ghi bị xóa (nó sẽ không công bằng ủng hộ các mục có id thấp hơn)

Có lẽ bạn nên sử dụng các phương thức ngẫu nhiên trong cơ sở dữ liệu của mình. Chúng khác nhau tùy thuộc vào DB bạn đang sử dụng, nhưng: order => "RAND ()" hoạt động cho mysql và: order => "RANDOM ()" hoạt động cho postgres

Model.first(:order => "RANDOM()") # postgres example

7
ĐẶT HÀNG B RNG RAND () cho MySQL kết thúc trong thời gian chạy khủng khiếp khi dữ liệu tăng lên. Không thể nhầm lẫn (tùy thuộc vào yêu cầu về thời gian) thậm chí chỉ bắt đầu từ hàng nghìn hàng.
Michael

Michael đưa ra một điểm tuyệt vời (điều đó cũng đúng với các DB khác). Nói chung, việc chọn các hàng ngẫu nhiên từ các bảng lớn không phải là điều bạn muốn làm trong một hành động động. Bộ nhớ đệm là bạn của bạn. Suy nghĩ lại về những gì bạn đang cố gắng thực hiện có thể không phải là một ý tưởng tồi.
semanticart

1
Đặt hàng RAND () trong mysql trên một bảng với khoảng một triệu hàng là slooooooooooooooooooooow.
Subimage

24
Không hoạt động nữa. Sử dụng Model.order("RANDOM()").firstthay thế.
phil pirozhkov

Chậm và cơ sở dữ liệu cụ thể. ActiveRecord được cho là hoạt động trơn tru giữa các cơ sở dữ liệu vì vậy bạn không nên sử dụng phương pháp này.
Dex

29

Điểm chuẩn hai phương pháp này trên MySQL 5.1,49, Ruby 1.9.2p180 trên bảng sản phẩm có hồ sơ + 5 triệu:

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)

Offset trong MySQL dường như chậm hơn nhiều.

EDIT tôi cũng đã thử

Product.first(:order => "RAND()")

Nhưng tôi đã phải giết nó sau ~ 60 giây. MySQL là "Sao chép vào bảng tmp trên đĩa". Điều đó sẽ không đi làm.


1
Đối với những người tìm kiếm thêm các thử nghiệm, một cách tiếp cận ngẫu nhiên thực sự mất bao lâu: Tôi đã thử Thing.order("RANDOM()").firsttrên một bảng có 250k mục - truy vấn kết thúc dưới nửa giây. (PostgreSQL 9.0, REE 1.8.7, 2 x 2,66 GHz) Điều đó đủ nhanh đối với tôi, vì tôi đang thực hiện "dọn dẹp" một lần.
Florian Pilz

6
Phương thức rand của Ruby trả về một số ít hơn số lượng được chỉ định để bạn muốn rand_id = rand(Product.count) + 1hoặc bạn sẽ không bao giờ nhận được bản ghi cuối cùng.
Ritchie

4
Lưu ý random1sẽ không hoạt động nếu bạn từng xóa một hàng trong bảng. (Đếm sẽ nhỏ hơn id tối đa và bạn sẽ không bao giờ có thể chọn các hàng có id cao).
Nicholas

Việc sử dụng random2có thể được cải thiện bằng #ordercách sử dụng một cột được lập chỉ mục.
Carson Rebke

18

Nó không phải là khó khăn.

ids = Model.pluck(:id)
random_model = Model.find(ids.sample)

plucktrả về một mảng của tất cả các id trong bảng. Các samplephương pháp trên mảng, trả về một id ngẫu nhiên từ mảng.

Điều này sẽ hoạt động tốt, với xác suất lựa chọn và hỗ trợ bằng nhau cho các bảng có hàng bị xóa. Bạn thậm chí có thể trộn nó với các ràng buộc.

User.where(favorite_day: "Friday").pluck(:id)

Và do đó chọn một người dùng ngẫu nhiên thích thứ sáu hơn là bất kỳ người dùng nào.


8
Điều này là sạch sẽ và hoạt động cho một bảng nhỏ hoặc sử dụng một lần, chỉ cần lưu ý rằng nó sẽ không quy mô. Trên bảng 3M, việc nhổ ID mất khoảng 15 giây đối với tôi trên MariaDB.
mahemoff

2
Đó là một điểm hay. Bạn đã tìm thấy một giải pháp thay thế nhanh hơn, trong khi vẫn duy trì các phẩm chất tương tự?
Niels B.

Không phải giải pháp bù được chấp nhận duy trì cùng một phẩm chất?
mahemoff

Không, nó không hỗ trợ các điều kiện và không có xác suất lựa chọn bằng nhau cho các bảng có bản ghi bị xóa.
Niels B.

1
Hãy nghĩ về nó, nếu bạn áp dụng các ràng buộc khi cả đếm và chọn với phần bù, kỹ thuật sẽ hoạt động. Tôi đã tưởng tượng chỉ áp dụng nó trên đếm.
Niels B.

15

Bạn không nên sử dụng giải pháp này, nhưng nếu vì lý do nào đó bạn thực sự muốn chọn ngẫu nhiên một bản ghi trong khi chỉ thực hiện một truy vấn cơ sở dữ liệu, bạn có thể sử dụng samplephương thức từ lớp Ruby Array , cho phép bạn chọn một mục ngẫu nhiên từ một mảng.

Model.all.sample

Phương pháp này chỉ yêu cầu truy vấn cơ sở dữ liệu, nhưng nó chậm hơn đáng kể so với các lựa chọn thay thế như Model.offset(rand(Model.count)).firstyêu cầu hai truy vấn cơ sở dữ liệu, mặc dù cách sau vẫn được ưu tiên.


99
Đừng làm điều này. Không bao giờ.
Zabba

5
Nếu bạn có 100k hàng trong cơ sở dữ liệu của mình, tất cả những hàng này sẽ phải được tải vào bộ nhớ.
Venkat D.

3
Tất nhiên nó không được khuyến nghị cho mã thời gian thực sản xuất, nhưng tôi thích giải pháp này, nó rất rõ ràng để sử dụng cho các tình huống đặc biệt như gieo mầm cơ sở dữ liệu với các giá trị giả.
fguillen

13
Xin vui lòng - không bao giờ nói không bao giờ. Đây là một giải pháp tuyệt vời để gỡ lỗi thời gian phát triển nếu bảng nhỏ. (Và nếu bạn đang lấy mẫu, việc gỡ lỗi hoàn toàn có thể là trường hợp sử dụng).
mahemoff

Tôi sử dụng để gieo hạt và là tốt cho tôi. Ngoài ra, Model.all.sample (n) cũng hoạt động :)
Arnaldo Ignacio Gaspar Véjar

13

Tôi đã tạo ra một rails 3 gem để xử lý việc này:

https://github.com/spilliton/randumb

Nó cho phép bạn làm những thứ như thế này:

Model.where(:column => "value").random(10)

7
Trong tài liệu về loại đá quý này, họ giải thích "randumb chỉ đơn giản là xử lý một bổ sung ORDER BY RANDOM()(hoặc RAND()cho mysql) cho truy vấn của bạn." - do đó, các nhận xét về hiệu suất xấu được đề cập trong các nhận xét cho câu trả lời của @semanticart cũng được áp dụng khi sử dụng loại đá quý này. Nhưng ít nhất nó là DB độc lập.
Nicolas

8

Tôi sử dụng điều này thường xuyên từ bảng điều khiển Tôi mở rộng ActiveRecord trong trình khởi tạo - ví dụ Rails 4:

class ActiveRecord::Base
  def self.random
    self.limit(1).offset(rand(self.count)).first
  end
end

Sau đó tôi có thể gọi Foo.randomđể mang lại một bản ghi ngẫu nhiên.


1
bạn có cần limit(1)không ActiveRecord#firstnên đủ thông minh để làm điều đó
tokland

6

Một truy vấn trong Postgres:

User.order('RANDOM()').limit(3).to_sql # Postgres example
=> "SELECT "users".* FROM "users" ORDER BY RANDOM() LIMIT 3"

Sử dụng một phần bù, hai truy vấn:

offset = rand(User.count) # returns an integer between 0 and (User.count - 1)
Model.offset(offset).limit(1)

1
Không cần -1, rand đếm tới num - 1
anemaria20

Cảm ơn, đã thay đổi: +1:
Thomas Klemm

5

Đọc tất cả những điều này không giúp tôi tự tin nhiều về việc cái nào sẽ hoạt động tốt nhất trong tình huống cụ thể của tôi với Rails 5 và MySQL / Maria 5.5. Vì vậy, tôi đã thử nghiệm một số câu trả lời trên ~ 65000 hồ sơ và có hai cách thực hiện:

  1. RAND () với một limitlà một người chiến thắng rõ ràng.
  2. Không sử dụng 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)

Câu trả lời này tổng hợp, xác thực và cập nhật câu trả lời của Mohamed , cũng như nhận xét của Nami WANG về cùng và nhận xét của Florian Pilz về câu trả lời được chấp nhận - vui lòng gửi phiếu bầu cho họ!


3

Bạn có thể sử dụng Arrayphương thức sample, phương thức sampletrả về một đối tượng ngẫu nhiên từ một mảng, để sử dụng nó, bạn chỉ cần thực hiện trong một ActiveRecordtruy vấn đơn giản trả về một bộ sưu tập, ví dụ:

User.all.sample

sẽ trả lại một cái gì đó như thế này:

#<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">

Tôi không khuyên bạn nên làm việc với các phương thức mảng trong khi sử dụng AR. Cách này mất gần 8 lần thời gian order('rand()').limit(1)thực hiện công việc "tương tự" (với ~ 10K hồ sơ).
Sebastian Palma

3

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. các User.order('RAND()').limit(10)chi phí 733.0ms.

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

  1. offsetphương pháp tiếp cận câu trả lời được chấp nhận 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.


2

Nếu bạn cần chọn một số kết quả ngẫu nhiên trong phạm vi được chỉ định :

scope :male_names, -> { where(sex: 'm') }
number_of_results = 10

rand = Names.male_names.pluck(:id).sample(number_of_results)
Names.where(id: rand)

1

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 }

Đây sẽ là một truy vấn nếu kích thước của mô hình đã được lưu trữ và hai cách khác.


1

Rails 4.2 và Oracle :

Đối với nhà tiên tri, bạn có thể đặt phạm vi trên Mô hình của mình như sau:

scope :random_order, -> {order('DBMS_RANDOM.RANDOM')}

hoặc là

scope :random_order, -> {order('DBMS_RANDOM.VALUE')}

Và sau đó cho một mẫu gọi nó như thế này:

Model.random_order.take(10)

hoặc là

Model.random_order.limit(5)

tất nhiên bạn cũng có thể đặt hàng mà không có phạm vi như vậy:

Model.all.order('DBMS_RANDOM.RANDOM') # or DBMS_RANDOM.VALUE respectively

Bạn có thể làm điều này với postgres với order('random()'và MySQL order('rand()')là tốt. Đây chắc chắn là câu trả lời tốt nhất.
jrochkind

1

Đối với cơ sở dữ liệu MySQL, hãy thử: Model.order ("RAND ()").


Điều này không hoạt động trên mysql .. bạn nên bao gồm ít nhất công cụ DB này có thể hoạt động với
Arnold Roa

Xin lỗi, đã có lỗi đánh máy. Đã sửa bây giờ. Nên hoạt động cho mysql (chỉ)
Vadim Eremeev

1

Nếu bạn đang sử dụng PostgreSQL 9.5+, bạn có thể tận dụng TABLESAMPLE để chọn một bản ghi ngẫu nhiên.

Hai phương thức lấy mẫu mặc định ( SYSTEMBERNOULLI) yêu cầu bạn chỉ định số lượng hàng trả về dưới dạng phần trăm của tổng số hàng trong bảng.

-- Fetch 10% of the rows in the customers table.
SELECT * FROM customers TABLESAMPLE BERNOULLI(10);

Điều này đòi hỏi phải biết số lượng hồ sơ trong bảng để chọn tỷ lệ phần trăm phù hợp, có thể không dễ dàng tìm thấy nhanh chóng. May mắn thay, có tsm_system_rowsmô-đun cho phép bạn chỉ định số lượng hàng để trả lại trực tiếp.

CREATE EXTENSION tsm_system_rows;

-- Fetch a single row from the customers table.
SELECT * FROM customers TABLESAMPLE SYSTEM_ROWS(1);

Để sử dụng điều này trong ActiveRecord, trước tiên hãy bật tiện ích mở rộng trong quá trình di chuyển:

class EnableTsmSystemRowsExtension < ActiveRecord::Migration[5.0]
  def change
    enable_extension "tsm_system_rows"
  end
end

Sau đó sửa đổi frommệnh đề của truy vấn:

customer = Customer.from("customers TABLESAMPLE SYSTEM_ROWS(1)").first

Tôi không biết SYSTEM_ROWSphương pháp lấy mẫu sẽ hoàn toàn ngẫu nhiên hay nếu nó chỉ trả về hàng đầu tiên từ một trang ngẫu nhiên.

Hầu hết các thông tin này được lấy từ một bài đăng trên blog của 2ndQuadrant được viết bởi Gulcin Yildirim .


1

Sau khi thấy rất nhiều câu trả lời, tôi quyết định chấm điểm tất cả chúng trên cơ sở dữ liệu PostgreSQL (9.6.3) của mình. Tôi sử dụng bảng 100.000 nhỏ hơn và loại bỏ Model.order ("RANDOM ()"). Đầu tiên vì nó đã chậm hơn hai bậc.

Sử dụng một bảng có 2.500.000 mục với 10 cột, người thắng cuộc là phương pháp nhổ nhanh hơn gần 8 lần so với người chạy lên (bù lại. Tôi chỉ chạy cái này trên một máy chủ cục bộ để con số đó có thể bị thổi phồng nhưng đủ lớn hơn để nhổ phương pháp là những gì tôi sẽ kết thúc bằng cách sử dụng. Điều đáng chú ý là điều này có thể gây ra vấn đề là bạn gảy nhiều hơn 1 kết quả tại một thời điểm vì mỗi một trong số chúng sẽ là duy nhất hay ít ngẫu nhiên hơn.

Pluck thắng chạy 100 lần trên bảng hàng 25.000.000 của tôi Chỉnh sửa: thực sự lần này bao gồm cả pluck trong vòng lặp nếu tôi lấy nó ra, nó chạy nhanh như lặp lại đơn giản trên id. Tuy nhiên; nó chiếm một lượng RAM khá lớn.

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)

Đây là dữ liệu chạy 2000 lần trên bảng 100.000 hàng của tôi để loại trừ ngẫu nhiên

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)

1

Câu hỏi rất cũ nhưng với:

rand_record = Model.all.shuffle

Bạn có một mảng bản ghi, sắp xếp theo thứ tự ngẫu nhiên. Không cần đá quý hoặc kịch bản.

Nếu bạn muốn một bản ghi:

rand_record = Model.all.shuffle.first

1
Không phải là lựa chọn tốt nhất, vì điều này tải tất cả các bản ghi vào bộ nhớ. Ngoài ra, shuffle.first==.sample
Andrew Rozhenko

0

Tôi hoàn toàn mới với RoR nhưng tôi đã làm việc này cho tôi:

 def random
    @cards = Card.all.sort_by { rand }
 end

Nó đến từ:

Làm cách nào để sắp xếp ngẫu nhiên (xáo trộn) một mảng trong Ruby?


4
Điều tệ hại là nó sẽ tải tất cả các thẻ từ cơ sở dữ liệu. Nó hiệu quả hơn để làm điều đó trong cơ sở dữ liệu.
Anton Kuzmin

Bạn cũng có thể xáo trộn mảng với array.shuffle. Dù sao, hãy cẩn thận, vì Card.allsẽ tải tất cả các bản ghi thẻ vào bộ nhớ, điều này càng kém hiệu quả khi chúng ta đang nói về nhiều đối tượng hơn.
Thomas Klemm

0

Phải làm gì:

rand_record = Model.find(Model.pluck(:id).sample)

Đối với tôi rất rõ ràng


0

Tôi thử ví dụ này của Sam trên Ứng dụng của mình bằng cách sử dụng rails 4.2.8 của Điểm chuẩn (Tôi đặt 1..C Category.count cho ngẫu nhiên, vì nếu ngẫu nhiên lấy 0, nó sẽ tạo ra lỗi (ActiveRecord :: RecordNotFound: Không thể tìm thấy Danh mục có 'id' = 0)) và của tôi là:

 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)

0

.order('RANDOM()').limit(limit)trông gọn gàng nhưng chậm đối với các bảng lớn vì nó cần tìm nạp và sắp xếp tất cả các hàng ngay cả khi limitlà 1 (bên trong cơ sở dữ liệu nhưng không phải trong Rails). Tôi không chắc chắn về MySQL nhưng điều này xảy ra trong Postgres. Giải thích thêm ở đâyđây .

Một giải pháp cho các bảng lớn là .from("products TABLESAMPLE SYSTEM(0.5)")nơi 0.5phương tiện 0.5%. Tuy nhiên, tôi thấy giải pháp này vẫn chậm nếu bạn có WHEREđiều kiện lọc ra rất nhiều hàng. Tôi đoán đó là vì TABLESAMPLE SYSTEM(0.5)lấy tất cả các hàng trước khi WHEREđiều kiện áp dụng.

Một giải pháp khác cho các bảng lớn (nhưng không ngẫu nhiên) là:

products_scope.limit(sample_size).sample(limit)

nơi sample_sizecó thể 100(nhưng không quá lớn nếu không nó sẽ chậm và tiêu tốn rất nhiều bộ nhớ), và limitcó thể 1. Lưu ý rằng mặc dù điều này nhanh nhưng nó không thực sự ngẫu nhiên, nó chỉ ngẫu nhiên trong sample_sizecác bản ghi.

PS: Kết quả điểm chuẩn trong các câu trả lời ở trên không đáng tin cậy (ít nhất là trong Postgres) vì một số truy vấn DB chạy ở lần thứ 2 có thể nhanh hơn đáng kể so với chạy ở lần đầu tiên, nhờ bộ đệm DB. Và thật không may, không có cách dễ dàng để vô hiệu hóa bộ đệm trong Postgres để làm cho các điểm chuẩn này đáng tin cậy.


0

Cùng với việc sử dụng RANDOM(), bạn cũng có thể ném nó vào một phạm vi:

class Thing
  scope :random, -> (limit = 1) {
    order('RANDOM()').
    limit(limit)
  }
end

Hoặc, nếu bạn không thích điều đó như một phạm vi, chỉ cần ném nó vào một phương thức lớp. Bây giờ Thing.randomlàm việc cùng với Thing.random(n).


0

Tùy thuộc vào ý nghĩa của "ngẫu nhiên" và những gì bạn thực sự muốn làm, takecó thể là đủ.

Theo "ý nghĩa" của ngẫu nhiên, ý tôi là:

  • Ý bạn là cho tôi bất kỳ yếu tố nào tôi không quan tâm đến vị trí của nó? thế là đủ
  • Bây giờ, nếu bạn có nghĩa là "cung cấp cho tôi bất kỳ yếu tố nào với xác suất hợp lý rằng các thử nghiệm lặp lại sẽ cho tôi các yếu tố khác với tập hợp" thì hãy buộc "May mắn" với bất kỳ phương pháp nào được đề cập trong các câu trả lời khác.

Ví dụ, để thử nghiệm, dữ liệu mẫu có thể được tạo ngẫu nhiên bằng mọi cách, như vậy takelà quá đủ, và phải trung thực, thậm chí first.

https://guides.rubyonrails.org/active_record_querying.html#take

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.