Rails 3: Rastgele Kayıt Alın


132

Bu yüzden, Rails 2'de rastgele bir kayıt bulmak için birkaç örnek buldum - tercih edilen yöntem şöyle görünüyor:

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

Acemi biri olarak, bunun Rails 3'teki yeni bul sözdizimi kullanılarak nasıl oluşturulacağından emin değilim.

Peki, rastgele bir kayıt bulmak için "Rails 3 Way" nedir?



9
^^ ancak özellikle sorunun tüm amacı olan Rails 3'ün en uygun yolunu arıyorum.
Andrew

rails 3'e özgü yalnızca sorgu zinciri :)
fl00r

Yanıtlar:


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

veya

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

Aslında, Rails 3'te tüm örnekler çalışacaktır. Ancak sipariş kullanmak RANDOMbüyük tablolar için oldukça yavaş ancak daha sql tarzı

UPD. Aşağıdaki numarayı indekslenmiş bir sütunda kullanabilirsiniz (PostgreSQL sözdizimi):

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

11
Yine de ilk örneğiniz MySQL'de çalışmayacak - MySQL için sözdizimi Thing.first (: order => "RAND ()") (ActiveRecord soyutlamalarını kullanmak yerine SQL yazma tehlikesi)
DanSingerman

@ DanSingerman, evet DB'ye özgü RAND()veya RANDOM(). Teşekkürler
fl00r

Ve dizinde eksik öğeler varsa bu sorun yaratmaz mı? (yığının ortasındaki bir şey silinirse, istenme şansı olur mu?
Victor S

@VictorS, hayır olmayacak # offset sadece bir sonraki mevcut kayda gider. Ruby 1.9.2 ve Rails 3.1 ile test ettim
SooDesuNe

1
@JohnMerlino, evet 0 ofset, id değil. Ofet 0, sıraya göre ilk öğe anlamına gelir.
fl00r

29

Db'nin localhost'ta olduğu ve kullanıcılar tablosunun 100K'dan biraz fazla kayıt içerdiği bir proje ( Rails 3.0.15, ruby ​​1.9.3-p125-perf ) üzerinde çalışıyorum .

kullanma

RAND'ye göre sipariş ()

oldukça yavaş

User.order ( "RAND (id)"). İlk

olur

SEÇİN users. * usersRANDA GÖRE SIRALAMA (id) SINIRI 1

ve yanıt vermesi 8 ila 12 saniye sürer !!

Rails günlüğü:

Kullanıcı Yükü (11030.8ms) SEÇİMİ users* RANDA usersGÖRE SIRALAMA () LİMİTİ 1

mysql'in açıklamasından

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

Hiçbir indeksin kullanılmadığını ( olası_anahtarlar = NULL ), geçici bir tablo oluşturulduğunu ve istenen değeri almak için fazladan bir geçiş gerektiğini görebilirsiniz ( ekstra = Geçici kullanma; Dosya sıralaması kullanma ).

Öte yandan, sorguyu ikiye bölerek ve Ruby kullanarak yanıt süresinde makul bir iyileşme elde ettik.

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

(; konsol kullanımı için nil)

Rails günlüğü:

Kullanıcı Yükü (25,2 ms) Kullanıcı Yükünden (0,2 ms) KİMLİĞİ usersSEÇİN users. * usersNEREDEN users. id= 106854 LİMİT 1

ve mysql açıklaması nedenini kanıtlıyor:

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

artık yalnızca dizinleri ve birincil anahtarı kullanabilir ve işi yaklaşık 500 kat daha hızlı yapabiliriz!

GÜNCELLEME:

icantbecool'un yorumlarda işaret ettiği gibi, tabloda silinmiş kayıtlar varsa yukarıdaki çözümün bir kusuru vardır.

Bir geçici çözüm olabilir

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

iki sorguya çeviren

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

ve yaklaşık 500 ms'de çalışır.


İkinci örneğinize "son" un sonrasına ".id" eklenmesi, "ID'siz Model bulunamadı" hatasını önleyecektir. Örneğin User.find (users.first (Random.rand (users.length)). Last.id)
turing_machine

Uyarı! In MySQL RAND(id)olacak DEĞİL farklı bir rastgele düzeni seni her sorguyu verir. Kullanım RAND()Farklı düzeni her sorgu istiyorum.
Justin Tanner

User.find (users.first (Random.rand (users.length)). Last.id), silinen bir kayıt varsa çalışmayacaktır. [1,2,4,5,] ve potansiyel olarak 3'ün kimliğini seçebilir, ancak aktif bir kayıt ilişkisi olmaz.
icantbecool

Ayrıca, users = User.scoped.select (: id); nil kullanımdan kaldırılmamıştır. Bunun yerine şunu kullanın: users = User.where (nil) .select (: id)
icantbecool

İlk parametre olarak Random.rand (users.length) kullanmanın bir hata olduğuna inanıyorum. Rastgele.rand, 0 döndürebilir. İlk parametre olarak 0 kullanıldığında, sınır sıfır olarak ayarlanır ve bu hiçbir kayıt döndürmez. Bunun yerine kullanılması gereken, users.length> 0 olduğu varsayılarak 1 + Random (users.length) olacaktır.
SWoo

12

Postgres kullanıyorsanız

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

MySQL kullanılıyorsa

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

Her iki durumda da Kullanıcılar tablosundan rastgele 5 kayıt seçiyorsunuz. Konsolda görüntülenen asıl SQL sorgusu burada.

SELECT * FROM users ORDER BY RANDOM() LIMIT 5

11

Bunu yapmak için büyük masalarda daha iyi performans gösteren ve ilişkileri ve kapsamları zincirlemenize izin veren bir 3 mücevher yaptım:

https://github.com/spilliton/randumb

(değiştir): Taşımın varsayılan davranışı temelde yukarıdaki ile aynı yaklaşımı kullanıyor, ancak isterseniz eski yöntemi kullanma seçeneğiniz var :)


6

Gönderilen yanıtların çoğu aslında oldukça büyük tablolarda (1 milyondan fazla satır) iyi performans göstermeyecektir. Rastgele sıralama hızlı bir şekilde birkaç saniye sürer ve masada bir sayım yapmak da oldukça uzun sürer.

Bu durumda benim için işe RANDOM()yarayan bir çözüm, nerede koşuluyla kullanmaktır:

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

Bir milyondan fazla satır içeren bir tabloda, bu sorgu genellikle 2 ms'den az sürer.


Çözümünüzün diğer bir avantajı, sorgu takeveren LIMIT(1)ancak dizi yerine tek öğe döndüren kullanım işlevidir . Bu yüzden first
çağırmamıza

Bana öyle geliyor ki, tablonun başındaki kayıtların bu şekilde seçilmiş olma olasılığı daha yüksektir, bu da elde etmek istediğiniz şey olmayabilir.
2018

5

İşte başlıyoruz

ray yolu

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

kullanım

Model.random #returns single random object

veya ikinci düşünce

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

kullanımı:

Model.random #returns shuffled collection

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

hiç kullanıcı yoksa ve 2 tane almak istiyorsanız, o zaman hatalar alırsınız. mantıklı olmak.
Tim Kretschmer

1
İkinci yaklaşım postgres ile işe yaramayacak, ancak "RANDOM()"bunun yerine kullanabilirsiniz ...
Daniel Richter

4

Bu benim için çok faydalı oldu ancak biraz daha esnekliğe ihtiyacım vardı, bu yüzden yaptığım şey buydu:

Örnek 1: Rastgele bir kayıt kaynağı bulmak : trevor turk site
Bunu Thing.rb modeline ekleyin

def self.random
    ids = connection.select_all("SELECT id FROM things")
    find(ids[rand(ids.length)]["id"].to_i) unless ids.blank?
end

denetleyicinizde böyle bir şey çağırabilirsiniz

@thing = Thing.random

Durum 2: Birden fazla rastgele kayıt bulma (tekrar yok) kaynağı:
Tekrar etmeyen 10 rastgele kayıt bulmam gerektiğini hatırlayamıyorum , bu yüzden
kontrol cihazınızda işe yaradığını buldum :

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

Bu 10 rastgele kayıt bulacaktır, ancak veritabanı özellikle büyükse (milyonlarca kayıt), bunun ideal olmayacağını ve performansın aksayacağını belirtmekte fayda var. Benim için yeterli olan birkaç bin kayda kadar iyi performans gösterecek.


4

Bir listeden rastgele bir öğe seçmek için Ruby yöntemi sample. sampleActiveRecord için verimli bir oluşturmak isterken ve önceki cevaplara dayanarak şunu kullandım:

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

Bunu koydum lib/ext/sample.rbve bununla birlikte yükledim config/initializers/monkey_patches.rb:

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

Aslında, #countbir için DB'ye bir arama yapacak COUNT. Kayıt zaten yüklenmişse, bu kötü bir fikir olabilir. Kullanılıp kullanılmayacağına ya da kayıt zaten yüklenmişse kullanılacağına #sizekarar vereceği için bunun yerine bir refaktör #countkullanılacaktır #length.
BenMorganIO

Geçtim countiçin sizegeri bildirimleri temel alarak. Daha fazla bilgi için: dev.mensfeld.pl/2014/09/…
Dan Kohn

3

Rails 5'te çalışır ve DB'den bağımsızdır:

Bu, kontrol cihazınızda:

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

Elbette, bunu burada gösterildiği gibi bir endişeye koyabilirsiniz .

Uygulamanın / modeller / kaygılar / 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

sonra...

Uygulamanın / modeller / book.rb

class Book < ActiveRecord::Base
  include Randomable
end

O zaman basitçe şunları yaparak kullanabilirsiniz:

Books.random

veya

Books.random(3)

Bu her zaman, en azından belgelenmesi gereken (kullanıcının istediği şey olmayabileceği için) sonraki kayıtları alır.
2018

2

ActiveRecord'da sample () kullanabilirsiniz

Örneğin

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

Kaynak: http://thinkingeek.com/2011/07/04/easily-select-random-records-rails/


33
Bu, çok miktarda kaydınız varsa kullanmak için çok kötü bir sorgu, çünkü DB TÜM kayıtları seçecek, ardından Rails bundan beş kayıt seçecek - büyük ölçüde israf.
DaveStephens

5
sampleActiveRecord'da değil, örnek Array'de. api.rubyonrails.org/classes/Array.html#method-i-sample
Frans

3
Bu, özellikle büyük bir tablodan rastgele kayıt almanın pahalı bir yoludur. Rails, tablonuzdaki her kayıt için belleğe bir nesne yükleyecektir. İspata ihtiyacınız varsa, 'rails console' çalıştırın, 'SomeModelFromYourApp.find (: all) .sample (5)' deneyin ve üretilen SQL'e bakın.
Eliot Sykes

1
Cevabımı görün, bu pahalı cevabı birden fazla rastgele kayıt almak için modern bir güzelliğe dönüştürür.
Arcolye

1

Oracle kullanıyorsanız

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

Çıktı

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

1

Çok sayıda veri satırı içeren tablo için özel olarak tasarlanmış rasgele kayıtlar için bu cevheri şiddetle tavsiye edin:

https://github.com/haopingfan/quick_random_records

Bu cevher dışında, diğer tüm cevaplar büyük veri tabanında kötü performans gösterir:

  1. quick_random_records yalnızca 4.6mstamamen maliyetlidir .

görüntü açıklamasını buraya girin

  1. kabul edilen cevap User.order('RAND()').limit(10)maliyeti 733.0ms.

görüntü açıklamasını buraya girin

  1. offsetyaklaşım maliyeti 245.4mstamamen.

görüntü açıklamasını buraya girin

  1. User.all.sample(10)yaklaşım maliyeti 573.4ms.

görüntü açıklamasını buraya girin

Not: Masamda sadece 120.000 kullanıcı var. Ne kadar çok kaydınız varsa, performans farkı o kadar büyük olacaktır.


GÜNCELLEME:

550.000 sıra ile masada performans sergileyin

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

görüntü açıklamasını buraya girin

  1. gem: quick_random_recordssadece maliyeti 6.4mstamamen

görüntü açıklamasını buraya girin


-2

Tablodan çok sayıda rastgele kayıt almanın çok kolay bir yolu. Bu, 2 ucuz sorgu yapar.

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

"3" ü istediğiniz rasgele kayıt sayısına değiştirebilirsiniz.


1
hayır, Model.pluck (: id] .sample (3) kısmı ucuz değil. Tablodaki her eleman için id alanını okuyacaktır.
Maximiliano Guzman

Veritabanından bağımsız daha hızlı bir yol var mı?
Arcolye

-5

DB'mden rastgele bir soru seçmek istediğim küçük bir uygulama geliştirirken bu sorunla karşılaştım. Kullandım:

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

Ve benim için iyi çalışıyor. Bu sadece küçük bir uygulama olduğu için daha büyük DB'ler için performansın nasıl olduğundan bahsedemem.


Evet, bu sadece tüm kayıtlarınızı alıyor ve üzerlerinde Ruby dizisi yöntemlerini kullanıyor. Buradaki dezavantaj, elbette, tüm kayıtlarınızı belleğe yüklemek, ardından bunları rasgele yeniden sıralamak ve ardından yeniden sıralanan dizideki ikinci öğeyi almak anlamına gelir. Büyük bir veri kümesiyle uğraşıyorsanız, bu kesinlikle bir bellek hogu olabilir. Küçük bir yana, neden ilk unsuru almıyorsunuz? (yani shuffle[0])
Andrew

karıştırılmalıdır [0]
Marcelo Avusturya
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.