Rekam acak dalam ActiveRecord

151

Saya perlu mendapatkan catatan acak dari tabel melalui ActiveRecord. Saya telah mengikuti contoh dari Jamis Buck dari 2006 .

Namun, saya juga menemukan cara lain melalui pencarian Google (tidak dapat menghubungkan dengan tautan karena batasan pengguna baru):

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

Saya ingin tahu bagaimana orang lain di sini telah melakukannya atau jika ada yang tahu cara apa yang akan lebih efisien.

jyunderwood
sumber
2
2 poin yang mungkin membantu jawaban. 1. Seberapa merata id Anda, apakah berurutan? 2. Seberapa acak itu perlu? Cukup baik acak, atau acak nyata?
Michael
Mereka adalah id berurutan yang dihasilkan secara otomatis oleh activerecord dan itu hanya harus cukup baik.
jyunderwood
1
Maka solusi yang Anda tawarkan mendekati ideal :) Saya akan menggunakan "SELECT MAX (id) FROM table_name" daripada COUNT (*) karena akan berurusan dengan baris yang dihapus sedikit lebih baik, jika tidak, sisanya baik-baik saja. Singkatnya, jika "cukup baik" ok, maka Anda hanya perlu memiliki metode yang mengasumsikan distribusi dekat dengan apa yang sebenarnya Anda miliki. Jika seragam dan bahkan seperti yang Anda katakan, rand sederhana berfungsi dengan baik.
Michael
1
Ini tidak akan berfungsi ketika Anda telah menghapus baris.
Venkat D.

Jawaban:

136

Saya belum menemukan cara yang ideal untuk melakukan ini tanpa setidaknya dua pertanyaan.

Berikut ini menggunakan angka yang dibuat secara acak (hingga jumlah catatan saat ini) sebagai offset .

offset = rand(Model.count)

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

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

Sejujurnya, saya baru saja menggunakan ORDER BY RAND () atau RANDOM () (tergantung pada database). Ini bukan masalah kinerja jika Anda tidak memiliki masalah kinerja.

Toby Hede
sumber
2
Kode Model.find(:offset => offset).firstakan melempar kesalahan. Saya pikir Model.first(:offset => offset)mungkin berkinerja lebih baik.
Harish Shetty
1
ya, saya telah bekerja dengan Rails 3 dan terus bingung tentang format kueri antar versi.
Toby Hede
7
Perhatikan bahwa menggunakan offset sangat lambat dengan dataset besar, karena sebenarnya membutuhkan pemindaian indeks (atau pemindaian tabel, jika indeks berkerumun digunakan seperti InnoDB). Dengan kata lain, ini adalah operasi O (N) tetapi "WHERE id> = # {rand_id} ORDER BY id ASC LIMIT 1" adalah O (log N), yang jauh lebih cepat.
kenn
15
Ketahuilah bahwa pendekatan penyeimbangan hanya menghasilkan satu titik data yang ditemukan secara acak (yang pertama, semua setelah itu masih diurutkan berdasarkan id). Jika Anda memerlukan beberapa catatan yang dipilih secara acak, Anda harus menggunakan pendekatan ini beberapa kali atau menggunakan metode urutan acak yang disediakan oleh database Anda, yaitu Thing.order("RANDOM()").limit(100)untuk 100 entri yang dipilih secara acak. (Ketahuilah bahwa itu ada RANDOM()di PostgreSQL dan RAND()di MySQL ... tidak portabel seperti yang Anda inginkan.)
Florian Pilz
3
Tidak berfungsi untuk saya di Rails 4. Gunakan Model.offset(offset).first.
mahemoff
206

Rel 6

Seperti yang dinyatakan oleh Jason dalam komentar, di Rails 6, argumen non-atribut tidak diperbolehkan. Anda harus membungkus nilai dalam Arel.sql()pernyataan.

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

Rails 5, 4

Di Rails 4 dan 5 , menggunakan Postgresql atau SQLite , menggunakan RANDOM():

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

Agaknya sama dengan MySQLRAND()

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

Ini sekitar 2,5 kali lebih cepat daripada pendekatan dalam jawaban yang diterima .

Peringatan : Ini lambat untuk kumpulan data besar dengan jutaan catatan, jadi Anda mungkin ingin menambahkan limitklausa.

Muhammad
sumber
4
"Random ()" juga berfungsi di sqlite, jadi bagi kita yang masih mengembangkan di sqlite dan menjalankan postgres dalam produksi, solusi Anda bekerja di kedua lingkungan.
wuliwong
5
Saya membuat tolok ukur untuk ini terhadap jawaban yang diterima. Pada Postgresql 9.4 pendekatan jawaban ini sekitar dua kali lebih cepat.
panmari
3
Sepertinya tidak direkomendasikan di mysql webtrenches.com/post.cfm/avoid-rand-in-mysql
Prakash Murthy
Ini adalah solusi tercepat
Sergio Belevskij
1
"Argumen non-atribut akan dianulir dalam Rails 6.0. Metode ini tidak boleh dipanggil dengan nilai-nilai yang disediakan pengguna, seperti parameter permintaan atau atribut model. Nilai-nilai yang diketahui aman dapat diteruskan dengan membungkusnya di Arel.sql ()."
Trenton Tyler
73

Kode contoh Anda akan mulai berperilaku tidak akurat setelah catatan dihapus (itu akan secara tidak adil mendukung item dengan id lebih rendah)

Anda mungkin lebih baik menggunakan metode acak di dalam basis data Anda. Ini bervariasi tergantung pada DB mana yang Anda gunakan, tetapi: order => "RAND ()" berfungsi untuk mysql dan: order => "RANDOM ()" bekerja untuk postgres

Model.first(:order => "RANDOM()") # postgres example
semanticart
sumber
7
ORDER BY RAND () untuk MySQL berakhir dengan runtime yang mengerikan saat data bertambah. Itu tidak dapat dipertahankan (tergantung pada persyaratan waktu) bahkan dimulai hanya pada ribuan baris.
Michael
Michael mengemukakan poin yang bagus (itu juga berlaku untuk DB lain). Umumnya memilih baris acak dari tabel besar bukanlah sesuatu yang ingin Anda lakukan dalam aksi dinamis. Caching adalah teman Anda. Memikirkan kembali apa yang ingin Anda capai mungkin juga bukan ide yang buruk.
semanticart
1
Memesan RAND () di mysql di atas meja dengan sekitar satu juta baris adalah slooooooooooooooooooooooow.
Subimage
24
Tidak berfungsi lagi. Gunakan Model.order("RANDOM()").firstsebagai gantinya.
phil pirozhkov
Lambat dan spesifik basis data. ActiveRecord seharusnya berfungsi dengan baik di antara basis data sehingga Anda tidak harus menggunakan metode ini.
Dex
29

Benchmarking kedua metode ini pada MySQL 5.1.49, Ruby 1.9.2p180 pada tabel produk dengan + 5 juta catatan:

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 di MySQL tampaknya jauh lebih lambat.

EDIT saya juga mencoba

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

Tapi aku harus membunuhnya setelah ~ 60 detik. MySQL adalah "Menyalin ke tabel tmp di disk". Itu tidak akan berhasil.

dkam
sumber
1
Bagi mereka yang mencari lebih banyak tes, berapa lama pendekatan acak yang sebenarnya diperlukan: Saya mencoba Thing.order("RANDOM()").firstdi atas meja dengan entri 250k - kueri selesai di bawah setengah detik. (PostgreSQL 9.0, REE 1.8.7, 2 x 2.66 GHz core) Cukup cepat untuk saya, karena saya melakukan "pembersihan" satu kali.
Florian Pilz
6
Metode rand Ruby mengembalikan satu kurang dari angka yang ditentukan sehingga Anda ingin rand_id = rand(Product.count) + 1atau Anda tidak akan pernah mendapatkan catatan terakhir.
Ritchie
4
Catatan random1tidak akan berfungsi jika Anda pernah menghapus baris di tabel. (Hitung akan kurang dari jumlah maksimum dan Anda tidak akan pernah dapat memilih baris dengan id tinggi).
Nicholas
Menggunakan random2dapat ditingkatkan dengan #ordermenggunakan kolom yang diindeks.
Carson Reinke
18

Tidak harus sulit.

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

pluckmengembalikan array dari semua id di dalam tabel. The samplemetode pada array, mengembalikan id acak dari array.

Ini harus berkinerja baik, dengan probabilitas pemilihan yang sama dan dukungan untuk tabel dengan baris yang dihapus. Anda bahkan dapat mencampurnya dengan kendala.

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

Dan dengan demikian memilih pengguna acak yang suka Jumat bukan hanya sembarang pengguna.

Niels B.
sumber
8
Ini bersih dan berfungsi untuk penggunaan meja kecil atau satu kali saja, perlu diketahui itu tidak akan skala. Di atas meja 3M, memetik ID membutuhkan waktu sekitar 15 detik untuk saya di MariaDB.
mahemoff
2
Itu poin yang bagus. Sudahkah Anda menemukan solusi alternatif yang lebih cepat, dengan tetap mempertahankan kualitas yang sama?
Niels B.
Tidakkah solusi offset yang diterima mempertahankan kualitas yang sama?
mahemoff
Tidak, itu tidak mendukung kondisi dan tidak memiliki probabilitas pemilihan yang sama untuk tabel dengan catatan yang dihapus.
Niels B.
1
Kalau dipikir-pikir, jika Anda menerapkan kendala saat menghitung dan memilih dengan offset, teknik ini harus bekerja. Saya membayangkan hanya menerapkannya pada hitungan.
Niels B.
15

Tidak disarankan agar Anda menggunakan solusi ini, tetapi jika karena alasan tertentu Anda benar - benar ingin secara acak memilih catatan sementara hanya membuat satu permintaan basis data, Anda bisa menggunakan samplemetode dari kelas Ruby Array , yang memungkinkan Anda memilih item acak dari sebuah array.

Model.all.sample

Metode ini hanya membutuhkan query database, tetapi secara signifikan lebih lambat daripada alternatif seperti Model.offset(rand(Model.count)).firstyang membutuhkan dua query database, meskipun yang terakhir masih lebih disukai.

Ryan Atallah
sumber
99
Jangan lakukan ini. Pernah.
Zabba
5
Jika Anda memiliki 100 ribu baris dalam basis data Anda, semua ini harus dimuat ke dalam memori.
Venkat D.
3
Tentu saja tidak direkomendasikan untuk kode waktu produksi, tetapi saya suka solusi ini, sangat jelas untuk digunakan untuk situasi khusus seperti penyemaian database dengan nilai-nilai palsu.
fguillen
13
Tolong - jangan pernah bilang tidak pernah. Ini adalah solusi yang bagus untuk debugging pengembangan-waktu jika tabelnya kecil. (Dan jika Anda mengambil sampel, debugging sangat mungkin adalah use case).
mahemoff
Saya menggunakan untuk penyemaian dan baik untuk saya. Selain itu, Model.all.sample (n) berfungsi juga :)
Arnaldo Ignacio Gaspar Véjar
13

Saya membuat permata 3 rails untuk menangani ini:

https://github.com/spilliton/randumb

Ini memungkinkan Anda melakukan hal-hal seperti ini:

Model.where(:column => "value").random(10)
tumpahan
sumber
7
Dalam dokumentasi permata ini mereka menjelaskan "randumb cukup mengolah tambahan ORDER BY RANDOM()(atau RAND()untuk mysql) ke kueri Anda." - Oleh karena itu, komentar tentang kinerja buruk yang disebutkan dalam komentar atas jawaban oleh @semanticart juga berlaku ketika menggunakan permata ini. Tapi setidaknya DB independen.
Nicolas
8

Saya sering menggunakan ini dari konsol saya memperpanjang ActiveRecord di initializer - Rails 4 contoh:

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

Saya kemudian dapat menelepon Foo.randomuntuk membawa kembali catatan acak.

Knotty66
sumber
1
yang Anda butuhkan limit(1)? ActiveRecord#firstharus cukup pintar untuk melakukan itu.
tokland
6

Satu permintaan di Postgres:

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

Menggunakan offset, dua kueri:

offset = rand(User.count) # returns an integer between 0 and (User.count - 1)
Model.offset(offset).limit(1)
Thomas Klemm
sumber
1
Tidak perlu -1, rand menghitung hingga num - 1
anemaria20
Terima kasih, diubah: +1:
Thomas Klemm
5

Membaca semua ini tidak memberi saya banyak kepercayaan tentang yang ini akan bekerja paling baik dalam situasi khusus saya dengan Rails 5 dan MySQL / Maria 5.5. Jadi saya menguji beberapa jawaban pada ~ 65000 catatan, dan meminta dua jawaban:

  1. RAND () dengan a limitadalah pemenang yang jelas.
  2. Jangan gunakan 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)

Jawaban ini mensintesis, memvalidasi dan memperbarui jawaban Mohamed , serta komentar Nami WANG tentang hal yang sama dan komentar Florian Pilz pada jawaban yang diterima - silakan kirim suara kepada mereka!

Sam
sumber
3

Anda dapat menggunakan Arraymetode ini sample, metode samplemengembalikan objek acak dari array, untuk menggunakannya Anda hanya perlu mengeksekusi dalam ActiveRecordkueri sederhana yang mengembalikan koleksi, misalnya:

User.all.sample

akan mengembalikan sesuatu seperti ini:

#<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">
trejo08
sumber
Saya tidak akan merekomendasikan bekerja dengan metode array saat menggunakan AR. Cara ini membutuhkan hampir 8 kali waktu order('rand()').limit(1)melakukan pekerjaan yang "sama" (dengan ~ 10 ribu catatan).
Sebastian Palma
3

Sangat merekomendasikan permata ini untuk catatan acak, yang dirancang khusus untuk tabel dengan banyak baris data:

https://github.com/haopingfan/quick_random_records

Semua jawaban lain berkinerja buruk dengan basis data besar, kecuali permata ini:

  1. quick_random_records hanya membutuhkan biaya 4.6mstotal.

masukkan deskripsi gambar di sini

  1. yang User.order('RAND()').limit(10)biaya 733.0ms.

masukkan deskripsi gambar di sini

  1. offsetpendekatan jawaban yang diterima menghabiskan biaya 245.4mstotal.

masukkan deskripsi gambar di sini

  1. yang User.all.sample(10)pendekatan biaya 573.4ms.

masukkan deskripsi gambar di sini


Catatan: Meja saya hanya memiliki 120.000 pengguna. Semakin banyak catatan yang Anda miliki, semakin besar perbedaan kinerja yang akan terjadi.

Derek Fan
sumber
2

Jika Anda perlu memilih beberapa hasil acak dalam ruang lingkup yang ditentukan :

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

rand = Names.male_names.pluck(:id).sample(number_of_results)
Names.where(id: rand)
Yuri Karpovich
sumber
1

Metode Ruby untuk memilih item secara acak dari daftar adalah sample. Ingin membuat efisien sampleuntuk ActiveRecord, dan berdasarkan jawaban sebelumnya, saya menggunakan:

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

Saya memasukkan ini ke dalam lib/ext/sample.rbdan kemudian memuatnya dengan ini di config/initializers/monkey_patches.rb:

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

Ini akan menjadi satu permintaan jika ukuran model sudah di-cache dan dua sebaliknya.

Dan Kohn
sumber
1

Rails 4.2 dan Oracle :

Untuk oracle Anda dapat mengatur ruang lingkup pada Model Anda seperti:

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

atau

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

Dan kemudian untuk sampel menyebutnya seperti ini:

Model.random_order.take(10)

atau

Model.random_order.limit(5)

tentu saja Anda juga bisa melakukan pemesanan tanpa ruang lingkup seperti:

Model.all.order('DBMS_RANDOM.RANDOM') # or DBMS_RANDOM.VALUE respectively
mahatmanich
sumber
Anda dapat melakukan ini dengan postgres order('random()'dan MySQL order('rand()')juga. Ini jelas merupakan jawaban terbaik.
jrochkind
1

Untuk basis data MySQL, coba: Model.order ("RAND ()"). Pertama

Vadim Eremeev
sumber
Ini tidak bekerja pada mysql .. Anda harus menyertakan setidaknya apa mesin DB seharusnya bekerja dengan
Arnold Roa
Maaf, ada kesalahan ketik. Diperbaiki sekarang Harus bekerja untuk mysql (hanya)
Vadim Eremeev
1

Jika Anda menggunakan PostgreSQL 9.5+, Anda dapat memanfaatkan TABLESAMPLEuntuk memilih catatan acak.

Dua metode pengambilan sampel default ( SYSTEMdan BERNOULLI) mengharuskan Anda menentukan jumlah baris untuk dikembalikan sebagai persentase dari total jumlah baris dalam tabel.

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

Ini membutuhkan mengetahui jumlah catatan dalam tabel untuk memilih persentase yang sesuai, yang mungkin tidak mudah ditemukan dengan cepat. Untungnya, ada tsm_system_rowsmodul yang memungkinkan Anda menentukan jumlah baris yang akan dikembalikan secara langsung.

CREATE EXTENSION tsm_system_rows;

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

Untuk menggunakan ini dalam ActiveRecord, pertama-tama aktifkan ekstensi dalam migrasi:

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

Kemudian ubah fromklausa dari kueri:

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

Saya tidak tahu apakah SYSTEM_ROWSmetode pengambilan sampel akan sepenuhnya acak atau hanya mengembalikan baris pertama dari halaman acak.

Sebagian besar informasi ini diambil dari posting blog 2ndQuadrant yang ditulis oleh Gulcin Yildirim .

Adam Sheehan
sumber
1

Setelah melihat begitu banyak jawaban, saya memutuskan untuk membandingkan semuanya pada database PostgreSQL (9.6.3) saya. Saya menggunakan meja yang lebih kecil 100.000 dan menyingkirkan Model.order ("RANDOM ()"). Pertama karena sudah dua perintah besarnya lebih lambat.

Menggunakan tabel dengan 2.500.000 entri dengan 10 kolom pemenangnya adalah metode pemetik hampir 8 kali lebih cepat dari runner up (offset. Saya hanya menjalankan ini di server lokal sehingga jumlahnya mungkin meningkat tetapi cukup besar sehingga memetiknya metode adalah apa yang saya akhirnya akan menggunakan. Ini juga perlu dicatat bahwa ini dapat menyebabkan masalah adalah Anda memetik lebih dari 1 hasil sekaligus karena masing-masing dari mereka akan menjadi unik alias kurang acak.

Pluck menang menjalankan 100 kali pada tabel baris 25.000.000 saya Sunting: sebenarnya kali ini termasuk memetik dalam loop jika saya mengeluarkannya itu berjalan sekitar secepat iterasi sederhana pada id. Namun; itu memakan cukup banyak 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)

Berikut adalah data yang berjalan 2000 kali pada tabel 100.000 baris saya untuk menyingkirkan acak

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)
Mendoza
sumber
1

Pertanyaan yang sangat lama tetapi dengan:

rand_record = Model.all.shuffle

Anda mendapat Array catatan, urutkan berdasarkan urutan acak. Tidak perlu permata atau skrip.

Jika Anda ingin satu catatan:

rand_record = Model.all.shuffle.first
Gregdebrick
sumber
1
Bukan pilihan terbaik, karena ini memuat semua rekaman ke dalam memori. Juga, shuffle.first==.sample
Andrew Rozhenko
0

Saya baru mengenal RoR, tetapi ini berhasil bagi saya:

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

Itu berasal dari:

Bagaimana cara mengurutkan (scramble) array secara acak di Ruby?

Aaron Pennington
sumber
4
Hal buruk tentang itu adalah bahwa ia akan memuat semua kartu dari database. Lebih efisien melakukannya di dalam basis data.
Anton Kuzmin
Anda juga dapat mengocok array dengan array.shuffle. Bagaimanapun, waspadalah, karena Card.allakan memuat semua catatan kartu ke dalam memori, yang semakin tidak efisien semakin banyak objek yang kita bicarakan.
Thomas Klemm
0

Apa yang akan dilakukan:

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

Bagi saya jauh lebih jelas

poramo
sumber
0

Saya mencoba ini contoh Sam di App saya menggunakan rail 4.2.8 dari Benchmark (saya meletakkan 1..Category.count untuk acak, karena jika acak mengambil 0 akan menghasilkan kesalahan (ActiveRecord :: RecordNotFound: Tidak dapat menemukan Kategori dengan 'id' = 0)) dan tambangnya adalah:

 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)
rld
sumber
0

.order('RANDOM()').limit(limit)terlihat rapi tetapi lambat untuk tabel besar karena perlu mengambil dan mengurutkan semua baris meskipun limit1 (secara internal dalam database tetapi tidak dalam Rails). Saya tidak yakin tentang MySQL tetapi ini terjadi di Postgres. Penjelasan lebih lanjut di sini dan di sini .

Salah satu solusi untuk tabel besar adalah di .from("products TABLESAMPLE SYSTEM(0.5)")mana 0.5berarti 0.5%. Namun, saya menemukan solusi ini masih lambat jika Anda memiliki WHEREkondisi yang menyaring banyak baris. Saya kira itu karena TABLESAMPLE SYSTEM(0.5)mengambil semua baris sebelumnyaWHERE ketentuan berlaku.

Solusi lain untuk tabel besar (tapi tidak terlalu acak) adalah:

products_scope.limit(sample_size).sample(limit)

di mana sample_sizebisa 100(tapi tidak terlalu besar kalau tidak lambat dan menghabiskan banyak memori), dan limitbisa 1. Perhatikan bahwa meskipun ini cepat tetapi tidak benar-benar acak, ini hanya acak dalam sample_sizecatatan.

PS: Hasil benchmark dalam jawaban di atas tidak dapat diandalkan (setidaknya dalam Postgres) karena beberapa permintaan DB yang berjalan pada waktu ke-2 dapat secara signifikan lebih cepat daripada berjalan pada waktu ke-1, berkat cache DB. Dan sayangnya tidak ada cara mudah untuk menonaktifkan cache di Postgres untuk membuat benchmark ini dapat diandalkan.

Dam Linh
sumber
0

Seiring dengan penggunaan RANDOM(), Anda juga dapat membuang ini ke dalam ruang lingkup:

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

Atau, jika Anda tidak suka itu sebagai ruang lingkup, cukup lemparkan ke metode kelas. Sekarang Thing.randombekerja bersama Thing.random(n).

Damien Roche
sumber
0

Tergantung dari arti "acak" dan apa yang sebenarnya ingin Anda lakukan, takebisa cukup.

Maksud "acak" yang saya maksud adalah:

  • Apakah maksud Anda memberi saya elemen yang tidak saya pedulikan posisinya? maka itu sudah cukup.
  • Sekarang, jika Anda bermaksud "beri saya elemen apa pun dengan probabilitas yang adil bahwa percobaan berulang akan memberi saya elemen berbeda dari set" maka, paksa "Keberuntungan" dengan salah satu metode yang disebutkan dalam jawaban lain.

Contoh, untuk pengujian, data sampel bisa saja dibuat secara acak, jadi takelebih dari cukup, dan jujur ​​saja first.

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

jgomo3
sumber