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.
ruby-on-rails
random
rails-activerecord
jyunderwood
sumber
sumber
Jawaban:
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 .
Sejujurnya, saya baru saja menggunakan ORDER BY RAND () atau RANDOM () (tergantung pada database). Ini bukan masalah kinerja jika Anda tidak memiliki masalah kinerja.
sumber
Model.find(:offset => offset).first
akan melempar kesalahan. Saya pikirModel.first(:offset => offset)
mungkin berkinerja lebih baik.Thing.order("RANDOM()").limit(100)
untuk 100 entri yang dipilih secara acak. (Ketahuilah bahwa itu adaRANDOM()
di PostgreSQL danRAND()
di MySQL ... tidak portabel seperti yang Anda inginkan.)Model.offset(offset).first
.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.Rails 5, 4
Di Rails 4 dan 5 , menggunakan Postgresql atau SQLite , menggunakan
RANDOM()
:Agaknya sama dengan MySQL
RAND()
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
limit
klausa.sumber
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
sumber
Model.order("RANDOM()").first
sebagai gantinya.Benchmarking kedua metode ini pada MySQL 5.1.49, Ruby 1.9.2p180 pada tabel produk dengan + 5 juta catatan:
Offset di MySQL tampaknya jauh lebih lambat.
EDIT saya juga mencoba
Tapi aku harus membunuhnya setelah ~ 60 detik. MySQL adalah "Menyalin ke tabel tmp di disk". Itu tidak akan berhasil.
sumber
Thing.order("RANDOM()").first
di 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.rand_id = rand(Product.count) + 1
atau Anda tidak akan pernah mendapatkan catatan terakhir.random1
tidak 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).random2
dapat ditingkatkan dengan#order
menggunakan kolom yang diindeks.Tidak harus sulit.
pluck
mengembalikan array dari semua id di dalam tabel. Thesample
metode 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.
Dan dengan demikian memilih pengguna acak yang suka Jumat bukan hanya sembarang pengguna.
sumber
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
sample
metode dari kelas Ruby Array , yang memungkinkan Anda memilih item acak dari sebuah array.Metode ini hanya membutuhkan query database, tetapi secara signifikan lebih lambat daripada alternatif seperti
Model.offset(rand(Model.count)).first
yang membutuhkan dua query database, meskipun yang terakhir masih lebih disukai.sumber
Saya membuat permata 3 rails untuk menangani ini:
https://github.com/spilliton/randumb
Ini memungkinkan Anda melakukan hal-hal seperti ini:
sumber
ORDER BY RANDOM()
(atauRAND()
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.Saya sering menggunakan ini dari konsol saya memperpanjang ActiveRecord di initializer - Rails 4 contoh:
Saya kemudian dapat menelepon
Foo.random
untuk membawa kembali catatan acak.sumber
limit(1)
?ActiveRecord#first
harus cukup pintar untuk melakukan itu.Satu permintaan di Postgres:
Menggunakan offset, dua kueri:
sumber
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:
limit
adalah pemenang yang jelas.pluck
+sample
.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!
sumber
Anda dapat menggunakan
Array
metode inisample
, metodesample
mengembalikan objek acak dari array, untuk menggunakannya Anda hanya perlu mengeksekusi dalamActiveRecord
kueri sederhana yang mengembalikan koleksi, misalnya:akan mengembalikan sesuatu seperti ini:
sumber
order('rand()').limit(1)
melakukan pekerjaan yang "sama" (dengan ~ 10 ribu catatan).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:
4.6ms
total.User.order('RAND()').limit(10)
biaya733.0ms
.offset
pendekatan jawaban yang diterima menghabiskan biaya245.4ms
total.User.all.sample(10)
pendekatan biaya573.4ms
.Catatan: Meja saya hanya memiliki 120.000 pengguna. Semakin banyak catatan yang Anda miliki, semakin besar perbedaan kinerja yang akan terjadi.
sumber
Jika Anda perlu memilih beberapa hasil acak dalam ruang lingkup yang ditentukan :
sumber
Metode Ruby untuk memilih item secara acak dari daftar adalah
sample
. Ingin membuat efisiensample
untuk ActiveRecord, dan berdasarkan jawaban sebelumnya, saya menggunakan:Saya memasukkan ini ke dalam
lib/ext/sample.rb
dan kemudian memuatnya dengan ini diconfig/initializers/monkey_patches.rb
:Ini akan menjadi satu permintaan jika ukuran model sudah di-cache dan dua sebaliknya.
sumber
Rails 4.2 dan Oracle :
Untuk oracle Anda dapat mengatur ruang lingkup pada Model Anda seperti:
atau
Dan kemudian untuk sampel menyebutnya seperti ini:
atau
tentu saja Anda juga bisa melakukan pemesanan tanpa ruang lingkup seperti:
sumber
order('random()'
dan MySQLorder('rand()')
juga. Ini jelas merupakan jawaban terbaik.Untuk basis data MySQL, coba: Model.order ("RAND ()"). Pertama
sumber
Jika Anda menggunakan PostgreSQL 9.5+, Anda dapat memanfaatkan
TABLESAMPLE
untuk memilih catatan acak.Dua metode pengambilan sampel default (
SYSTEM
danBERNOULLI
) mengharuskan Anda menentukan jumlah baris untuk dikembalikan sebagai persentase dari total jumlah baris dalam tabel.Ini membutuhkan mengetahui jumlah catatan dalam tabel untuk memilih persentase yang sesuai, yang mungkin tidak mudah ditemukan dengan cepat. Untungnya, ada
tsm_system_rows
modul yang memungkinkan Anda menentukan jumlah baris yang akan dikembalikan secara langsung.Untuk menggunakan ini dalam ActiveRecord, pertama-tama aktifkan ekstensi dalam migrasi:
Kemudian ubah
from
klausa dari kueri:Saya tidak tahu apakah
SYSTEM_ROWS
metode 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 .
sumber
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.
Berikut adalah data yang berjalan 2000 kali pada tabel 100.000 baris saya untuk menyingkirkan acak
sumber
Pertanyaan yang sangat lama tetapi dengan:
Anda mendapat Array catatan, urutkan berdasarkan urutan acak. Tidak perlu permata atau skrip.
Jika Anda ingin satu catatan:
sumber
shuffle.first
==.sample
Saya baru mengenal RoR, tetapi ini berhasil bagi saya:
Itu berasal dari:
Bagaimana cara mengurutkan (scramble) array secara acak di Ruby?
sumber
array.shuffle
. Bagaimanapun, waspadalah, karenaCard.all
akan memuat semua catatan kartu ke dalam memori, yang semakin tidak efisien semakin banyak objek yang kita bicarakan.Apa yang akan dilakukan:
Bagi saya jauh lebih jelas
sumber
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:
sumber
.order('RANDOM()').limit(limit)
terlihat rapi tetapi lambat untuk tabel besar karena perlu mengambil dan mengurutkan semua baris meskipunlimit
1 (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)")
mana0.5
berarti0.5%
. Namun, saya menemukan solusi ini masih lambat jika Anda memilikiWHERE
kondisi yang menyaring banyak baris. Saya kira itu karenaTABLESAMPLE SYSTEM(0.5)
mengambil semua baris sebelumnyaWHERE
ketentuan berlaku.Solusi lain untuk tabel besar (tapi tidak terlalu acak) adalah:
di mana
sample_size
bisa100
(tapi tidak terlalu besar kalau tidak lambat dan menghabiskan banyak memori), danlimit
bisa1
. Perhatikan bahwa meskipun ini cepat tetapi tidak benar-benar acak, ini hanya acak dalamsample_size
catatan.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.
sumber
Seiring dengan penggunaan
RANDOM()
, Anda juga dapat membuang ini ke dalam ruang lingkup:Atau, jika Anda tidak suka itu sebagai ruang lingkup, cukup lemparkan ke metode kelas. Sekarang
Thing.random
bekerja bersamaThing.random(n)
.sumber
Tergantung dari arti "acak" dan apa yang sebenarnya ingin Anda lakukan,
take
bisa cukup.Maksud "acak" yang saya maksud adalah:
Contoh, untuk pengujian, data sampel bisa saja dibuat secara acak, jadi
take
lebih dari cukup, dan jujur sajafirst
.https://guides.rubyonrails.org/active_record_querying.html#take
sumber