Znalazłem więc kilka przykładów wyszukiwania losowego rekordu w Railsach 2 - preferowaną metodą wydaje się być:
Thing.find :first, :offset => rand(Thing.count)
Będąc nowicjuszem, nie jestem pewien, jak można to skonstruować przy użyciu nowej składni wyszukiwania w Railsach 3.
Więc co to jest "Rails 3 Way", aby znaleźć losowy rekord?
Odpowiedzi:
Thing.first(:order => "RANDOM()") # For MySQL :order => "RAND()", - thanx, @DanSingerman # Rails 3 Thing.order("RANDOM()").first
lub
Thing.first(:offset => rand(Thing.count)) # Rails 3 Thing.offset(rand(Thing.count)).first
Właściwie w Railsach 3 wszystkie przykłady będą działać. Ale używanie kolejności
RANDOM
jest dość powolne w przypadku dużych tabel, ale bardziej w stylu sqlUPD. Możesz użyć następującej sztuczki na indeksowanej kolumnie (składnia PostgreSQL):
select * from my_table where id >= trunc( random() * (select max(id) from my_table) + 1 ) order by id limit 1;
źródło
RAND()
lubRANDOM()
. DziękiPracuję nad projektem ( Rails 3.0.15, ruby 1.9.3-p125-perf ), w którym baza danych znajduje się w localhost, a tablica użytkowników ma nieco ponad 100K rekordów .
Za pomocą
jest dość powolny
staje się
a odpowiedź zajmuje od 8 do 12 sekund !!
Dziennik Railsów:
z wyjaśnienia 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 | +----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
Widać, że żaden indeks nie jest używany ( possible_keys = NULL ), tworzona jest tymczasowa tabela i wymagane jest dodatkowe przejście, aby pobrać żądaną wartość ( extra = Usingporary; Using filesort ).
Z drugiej strony, dzieląc zapytanie na dwie części i używając Rubiego, mamy znaczną poprawę czasu odpowiedzi.
users = User.scoped.select(:id);nil User.find( users.first( Random.rand( users.length )).last )
(; zero w przypadku użycia konsoli)
Dziennik Railsów:
a wyjaśnienie mysql udowadnia, dlaczego:
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+ | 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 | | +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
możemy teraz używać tylko indeksów i klucza podstawowego i wykonywać to zadanie około 500 razy szybciej!
AKTUALIZACJA:
jak zauważył icantbecool w komentarzach powyższe rozwiązanie ma wadę, jeśli w tabeli są usunięte rekordy.
Może to być obejście
users_count = User.count User.scoped.limit(1).offset(rand(users_count)).first
co przekłada się na dwa zapytania
SELECT COUNT(*) FROM `users` SELECT `users`.* FROM `users` LIMIT 1 OFFSET 148794
i działa w około 500 ms.
źródło
RAND(id)
będzie NIE daje innym losowo za każdym zapytaniu. Użyj,RAND()
jeśli chcesz, aby każde zapytanie miało inną kolejność.Jeśli używasz Postgres
User.limit(5).order("RANDOM()")
Jeśli używasz MySQL
User.limit(5).order("RAND()")
W obu przypadkach wybierasz losowo 5 rekordów z tabeli Users. Oto rzeczywiste zapytanie SQL wyświetlane w konsoli.
SELECT * FROM users ORDER BY RANDOM() LIMIT 5
źródło
Zrobiłem za to klejnot rails 3, który działa lepiej na dużych stołach i pozwala łączyć relacje i zakresy:
https://github.com/spilliton/randumb
(edytuj): Domyślne zachowanie mojego klejnotu w zasadzie używa tego samego podejścia, co teraz, ale możesz użyć starego sposobu, jeśli chcesz :)
źródło
Wiele z opublikowanych odpowiedzi w rzeczywistości nie będzie dobrze działać na dość dużych tabelach (ponad milion wierszy). Losowe zamówienie zajmuje szybko kilka sekund, a liczenie na stole również zajmuje dość dużo czasu.
Rozwiązaniem, które dobrze się sprawdza w tej sytuacji, jest użycie
RANDOM()
warunku gdzie:Thing.where('RANDOM() >= 0.9').take
W przypadku tabeli zawierającej ponad milion wierszy to zapytanie zajmuje zwykle mniej niż 2 ms.
źródło
take
funkcji, która dajeLIMIT(1)
zapytanie, ale zwraca pojedynczy element zamiast tablicy. Więc nie musimy wzywaćfirst
No to ruszamy
szyny sposób
#in your initializer module ActiveRecord class Base def self.random if (c = count) != 0 find(:first, :offset =>rand(c)) end end end end
stosowanie
Model.random #returns single random object
albo druga myśl jest
module ActiveRecord class Base def self.random order("RAND()") end end end
stosowanie:
Model.random #returns shuffled collection
źródło
Couldn't find all Users with 'id': (first, {:offset=>1}) (found 0 results, but was looking for 2)
"RANDOM()"
zamiast tego użyć ...Było to dla mnie bardzo przydatne, ale potrzebowałem nieco większej elastyczności, więc zrobiłem to:
Przypadek 1: Znalezienie jednego losowego źródła rekordu : witryna trevor turk
Dodaj to do modelu 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
wtedy w kontrolerze możesz wywołać coś takiego
Przypadek 2: Znalezienie wielu losowych rekordów (bez powtórzeń) Źródło: nie pamiętam
, potrzebowałem znaleźć 10 losowych rekordów bez powtórzeń, więc znalazłem, że zadziałało w
twoim kontrolerze:
thing_ids = Thing.find( :all, :select => 'id' ).map( &:id ) @things = Thing.find( (1..10).map { thing_ids.delete_at( thing_ids.size * rand ) } )
Znajdzie to 10 losowych rekordów, jednak warto wspomnieć, że jeśli baza danych jest szczególnie duża (miliony rekordów), nie byłoby to idealne, a wydajność będzie utrudniona. Zagra dobrze do kilku tysięcy płyt, co mi wystarczyło.
źródło
Metoda Ruby do losowego wybierania pozycji z listy to
sample
. Chcąc stworzyć wydajnysample
dla ActiveRecord i na podstawie wcześniejszych odpowiedzi użyłem:module ActiveRecord class Base def self.sample offset(rand(size)).first end end end
Umieszczam to,
lib/ext/sample.rb
a następnie ładuję to wconfig/initializers/monkey_patches.rb
:Dir[Rails.root.join('lib/ext/*.rb')].each { |file| require file }
źródło
#count
wykona wywołanie DB dla plikuCOUNT
. Jeśli rekord jest już załadowany, może to być zły pomysł.#size
Zamiast tego należy użyć refaktora, ponieważ zdecyduje, czy#count
należy go użyć, czy, jeśli rekord jest już załadowany, użyć#length
.count
nasize
na podstawie Twoich opinii. Więcej informacji na: dev.mensfeld.pl/2014/09/…Działa w Rails 5 i jest agnostykiem DB:
To w twoim kontrolerze:
@quotes = Quote.offset(rand(Quote.count - 3)).limit(3)
Możesz oczywiście umieścić to w obawie, jak pokazano tutaj .
app / models / problems / 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
następnie...
app / models / book.rb
class Book < ActiveRecord::Base include Randomable end
Następnie możesz użyć po prostu, wykonując:
lub
Books.random(3)
źródło
Możesz użyć sample () w ActiveRecord
Na przykład
def get_random_things_for_home_page find(:all).sample(5) end
Źródło: http://thinkingeek.com/2011/07/04/easily-select-random-records-rails/
źródło
sample
nie ma w ActiveRecord, sample znajduje się w Array. api.rubyonrails.org/classes/Array.html#method-i-sampleJeśli używasz Oracle
User.limit(10).order("DBMS_RANDOM.VALUE")
Wynik
SELECT * FROM users ORDER BY DBMS_RANDOM.VALUE WHERE ROWNUM <= 10
źródło
Zdecydowanie polecam ten klejnot dla losowych rekordów, który jest specjalnie zaprojektowany dla tabel z dużą ilością wierszy danych:
https://github.com/haopingfan/quick_random_records
Wszystkie inne odpowiedzi działają źle z dużą bazą danych, z wyjątkiem tego klejnotu:
4.6ms
całkowicie.User.order('RAND()').limit(10)
koszt zaakceptowanej odpowiedzi733.0ms
.offset
podejście kosztować245.4ms
całkowicie.User.all.sample(10)
koszt podejście573.4ms
.Uwaga: moja tabela ma tylko 120 000 użytkowników. Im więcej masz płyt, tym większa będzie różnica w wydajności.
AKTUALIZACJA:
Wykonaj na stole zawierającym 550 000 wierszy
Model.where(id: Model.pluck(:id).sample(10))
koszt1384.0ms
gem: quick_random_records
tylko kosztować6.4ms
całkowicieźródło
Bardzo łatwy sposób na pobranie wielu losowych rekordów z tabeli. To daje 2 tanie zapytania.
Model.where(id: Model.pluck(:id).sample(3))
Możesz zmienić liczbę „3” na wybraną liczbę losowych rekordów.
źródło
Właśnie napotkałem ten problem podczas tworzenia małej aplikacji, w której chciałem wybrać losowe pytanie z mojej bazy danych. Użyłem:
@question1 = Question.where(:lesson_id => params[:lesson_id]).shuffle[1]
I to działa dobrze dla mnie. Nie mogę mówić o wydajności większych baz danych, ponieważ jest to tylko mała aplikacja.
źródło
shuffle[0]
)