Potrzebuję losowego rekordu z tabeli za pośrednictwem ActiveRecord. Wziąłem za przykładem Jamisa Bucka z 2006 roku .
Jednak natknąłem się również na inny sposób przez wyszukiwarkę Google (nie mogę przypisać linku z powodu nowych ograniczeń użytkowników):
rand_id = rand(Model.count)
rand_record = Model.first(:conditions => ["id >= ?", rand_id])
Ciekaw jestem, jak zrobili to inni tutaj lub czy ktoś wie, jaki sposób byłby bardziej wydajny.
ruby-on-rails
random
rails-activerecord
jyunderwood
źródło
źródło
Odpowiedzi:
Nie znalazłem idealnego sposobu na zrobienie tego bez co najmniej dwóch zapytań.
W poniższym przykładzie zastosowano losowo wygenerowaną liczbę (aż do bieżącej liczby rekordów) jako przesunięcie .
Szczerze mówiąc, właśnie używałem ORDER BY RAND () lub RANDOM () (w zależności od bazy danych). Nie jest to problem z wydajnością, jeśli nie masz problemu z wydajnością.
źródło
Model.find(:offset => offset).first
zgłosi błąd. Myślę, żeModel.first(:offset => offset)
może działać lepiej.Thing.order("RANDOM()").limit(100)
Dla 100 losowo wybranych wpisów. (Pamiętaj, że jestRANDOM()
w PostgreSQL iRAND()
MySQL ... nie jest tak przenośny, jak byś chciał).Model.offset(offset).first
.Szyny 6
Jak stwierdził Jason w komentarzach, w Railsach 6 argumenty niebędące atrybutami są niedozwolone. Musisz opakować wartość w
Arel.sql()
instrukcję.Szyny 5, 4
W Railsach 4 i 5 , używając Postgresql lub SQLite , używając
RANDOM()
:Przypuszczalnie to samo działałoby dla MySQL z
RAND()
To około 2,5 raza szybciej niż podejście w przyjętej odpowiedzi .
Ostrzeżenie : jest to powolne w przypadku dużych zbiorów danych z milionami rekordów, więc warto dodać
limit
klauzulę.źródło
Twój przykładowy kod zacznie działać niepoprawnie po usunięciu rekordów (będzie nieuczciwie faworyzował elementy o niższych identyfikatorach)
Prawdopodobnie lepiej będzie, jeśli użyjesz losowych metod w swojej bazie danych. Różnią się one w zależności od używanej bazy danych, ale: order => "RAND ()" działa dla mysql i: order => "RANDOM ()" działa dla postgres
źródło
Model.order("RANDOM()").first
zamiast tego.Test porównawczy tych dwóch metod w MySQL 5.1.49, Ruby 1.9.2p180 na tabeli produktów z + 5 milionami rekordów:
Offset w MySQL wydaje się być znacznie wolniejszy.
EDYCJA Próbowałem też
Ale musiałem go zabić po ~ 60 sekundach. MySQL było „Kopiowanie do tabeli tmp na dysku”. To nie zadziała.
źródło
Thing.order("RANDOM()").first
na tabeli z 250 tys. Wpisów - zapytanie zakończyło się poniżej pół sekundy. (PostgreSQL 9.0, REE 1.8.7, 2 rdzenie 2,66 GHz) To dla mnie wystarczająco szybkie, ponieważ przeprowadzam jednorazowe „czyszczenie”.rand_id = rand(Product.count) + 1
przeciwnym razie nigdy nie otrzymasz ostatniego rekordu.random1
nie będzie działać, jeśli kiedykolwiek usuniesz wiersz w tabeli. (Liczba będzie mniejsza niż maksymalny identyfikator i nigdy nie będzie można wybrać wierszy z wysokimi identyfikatorami).random2
można poprawić,#order
używając kolumny indeksowanej.To nie musi być takie trudne.
pluck
zwraca tablicę wszystkich identyfikatorów w tabeli.sample
Sposób na tablicy powraca losowy identyfikator z tablicy.Powinno to działać dobrze, z równym prawdopodobieństwem wyboru i obsługi tabel z usuniętymi wierszami. Możesz nawet mieszać to z ograniczeniami.
W ten sposób wybierz przypadkowego użytkownika, który lubi piątki, a nie dowolnego użytkownika.
źródło
Nie zaleca się korzystania z tego rozwiązania, ale jeśli z jakiegoś powodu naprawdę chcesz losowo wybrać rekord podczas wykonywania tylko jednego zapytania do bazy danych, możesz skorzystać z
sample
metody z klasy Ruby Array , która pozwala na wybranie losowego elementu z tablicy.Ta metoda wymaga tylko zapytania do bazy danych, ale jest znacznie wolniejsza niż alternatywy, takie jak
Model.offset(rand(Model.count)).first
wymagające dwóch zapytań do bazy danych, chociaż ta ostatnia jest nadal preferowana.źródło
Zrobiłem klejnot szyn 3, aby sobie z tym poradzić:
https://github.com/spilliton/randumb
Pozwala robić takie rzeczy:
źródło
ORDER BY RANDOM()
(lubRAND()
dla mysql) do zapytania”. - dlatego komentarze dotyczące złego działania wymienione w komentarzach do odpowiedzi @semanticart obowiązują również podczas korzystania z tego klejnotu. Ale przynajmniej jest niezależny od DB.Używam tego tak często z konsoli, że rozszerzam ActiveRecord w inicjatorze - przykład Rails 4:
Mogę wtedy zadzwonić
Foo.random
i przynieść losowy rekord.źródło
limit(1)
?ActiveRecord#first
powinien być wystarczająco inteligentny, aby to zrobić.Jedno zapytanie w Postgres:
Używając przesunięcia, dwa zapytania:
źródło
Przeczytanie ich wszystkich nie dało mi wiele pewności, które z nich najlepiej sprawdzą się w mojej konkretnej sytuacji z Railsami 5 i MySQL / Maria 5.5. Przetestowałem więc niektóre odpowiedzi na ~ 65000 rekordach i mam dwa wnioski:
limit
jest wyraźnym zwycięzcą.pluck
+sample
.Ta odpowiedź syntetyzuje, weryfikuje i aktualizuje odpowiedź Mohameda , a także komentarz Nami WANG na ten sam temat oraz komentarz Floriana Pilza na temat zaakceptowanej odpowiedzi - prosimy o przesyłanie im głosów!
źródło
Możesz użyć
Array
metodysample
, metodasample
zwraca losowy obiekt z tablicy, aby go użyć wystarczy wykonać w prostymActiveRecord
zapytaniu zwracającym kolekcję, na przykład:zwróci coś takiego:
źródło
order('rand()').limit(1)
tej samej pracy (przy ~ 10K rekordów).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)
koszt733.0ms
.offset
kosztowała245.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.
źródło
Jeśli chcesz wybrać losowe wyniki w określonym zakresie :
źródło
Metoda Ruby do losowego wybierania pozycji z listy to
sample
. Chcąc stworzyć wydajnysample
dla ActiveRecord i na podstawie poprzednich odpowiedzi użyłem:Umieszczam to,
lib/ext/sample.rb
a następnie ładuję to wconfig/initializers/monkey_patches.rb
:Będzie to jedno zapytanie, jeśli rozmiar modelu jest już buforowany, a dwa w innym przypadku.
źródło
Rails 4.2 i Oracle :
W przypadku Oracle możesz ustawić zakres na swoim modelu w następujący sposób:
lub
A potem dla próbki nazwij to tak:
lub
oczywiście możesz też złożyć zamówienie bez zakresu:
źródło
order('random()'
i MySQL zorder('rand()')
. To zdecydowanie najlepsza odpowiedź.W przypadku bazy danych MySQL spróbuj najpierw: Model.order ("RAND ()")
źródło
Jeśli używasz PostgreSQL 9.5+, możesz
TABLESAMPLE
wybrać losowy rekord.Dwie domyślne metody próbkowania (
SYSTEM
iBERNOULLI
) wymagają określenia liczby wierszy do zwrócenia jako procent całkowitej liczby wierszy w tabeli.Wymaga to znajomości ilości rekordów w tabeli, aby wybrać odpowiednią wartość procentową, co może nie być łatwe do szybkiego znalezienia. Na szczęście istnieje
tsm_system_rows
moduł, który pozwala określić liczbę wierszy do bezpośredniego zwrotu.Aby użyć tego w ActiveRecord, najpierw włącz rozszerzenie w ramach migracji:
Następnie zmodyfikuj
from
klauzulę zapytania:Nie wiem, czy
SYSTEM_ROWS
metoda próbkowania będzie całkowicie losowa, czy po prostu zwróci pierwszy wiersz z losowej strony.Większość tych informacji pochodzi z posta na blogu 2ndQuadrant napisanego przez Gulcina Yildirima .
źródło
Po zobaczeniu tak wielu odpowiedzi zdecydowałem się je wszystkie porównać w mojej bazie danych PostgreSQL (9.6.3). Używam mniejszej tabeli 100 000 i najpierw pozbyłem się Model.order („RANDOM ()”), ponieważ był już o dwa rzędy wielkości wolniejszy.
Korzystając z tabeli z 2500000 wpisów z 10 kolumnami, zwycięzcą była metoda zrywania, która była prawie 8 razy szybsza niż druga osoba (przesunięcie. Uruchomiłem to tylko na lokalnym serwerze, więc liczba może być zawyżona, ale jest na tyle większa, że metoda jest tym, czego ostatecznie użyję. Warto również zauważyć, że może to powodować problemy, jeśli wyrywasz więcej niż 1 wynik na raz, ponieważ każdy z nich będzie niepowtarzalny, czyli mniej losowy.
Pluck wygrywa z uruchomieniem 100 razy na mojej tabeli zawierającej 25 000 000 wierszy Edytuj: w rzeczywistości ten czas obejmuje zrywanie w pętli, jeśli to wyciągnę, działa mniej więcej tak szybko, jak prosta iteracja na id. Jednak; zajmuje sporo pamięci RAM.
Oto dane działające 2000 razy w mojej tabeli zawierającej 100 000 wierszy, aby wykluczyć losowość
źródło
Bardzo stare pytanie, ale z:
Masz tablicę rekordów, posortuj według losowej kolejności. Nie potrzebujesz klejnotów ani skryptów.
Jeśli chcesz jeden rekord:
źródło
shuffle.first
==.sample
Jestem zupełnie nowy w RoR, ale udało mi się to:
To przyszło z:
Jak losowo posortować (wymieszać) tablicę w Rubim?
źródło
array.shuffle
. W każdym razie uważaj, ponieważCard.all
załadują wszystkie rekordy karty do pamięci, co staje się mniej wydajne, im więcej obiektów, o których mówimy.Co do zrobienia:
Dla mnie jest dużo jasne
źródło
Próbuję tego z przykładu Sama w mojej aplikacji przy użyciu rails 4.2.8 Benchmark (wstawiam 1..Category.count dla losowego, ponieważ jeśli losowa przyjmuje 0, spowoduje to błąd (ActiveRecord :: RecordNotFound: Could not find Kategoria z 'id' = 0)), a moja:
źródło
.order('RANDOM()').limit(limit)
wygląda schludnie, ale działa wolno w przypadku dużych tabel, ponieważ musi pobrać i posortować wszystkie wiersze, nawet jeślilimit
ma wartość 1 (wewnętrznie w bazie danych, ale nie w Railsach). Nie jestem pewien co do MySQL, ale dzieje się tak w Postgres. Więcej wyjaśnień tutaj i tutaj .Jednym z rozwiązań dla dużych stołów jest
.from("products TABLESAMPLE SYSTEM(0.5)")
to, co0.5
oznacza0.5%
. Jednak uważam, że to rozwiązanie jest nadal powolne, jeśli maszWHERE
warunki, które odfiltrowują wiele wierszy. Wydaje mi się, że to dlatego, że wcześniejTABLESAMPLE SYSTEM(0.5)
pobierz wszystkie wierszeWHERE
zastosowaniem warunków.Innym rozwiązaniem dla dużych tabel (ale niezbyt losowych) jest:
gdzie
sample_size
może być100
(ale nie za duży, w przeciwnym razie jest powolny i zużywa dużo pamięci) ilimit
może być1
. Zauważ, że chociaż jest to szybkie, ale nie jest to przypadkowe, jest losowesample_size
tylko w rekordach.PS: Wyniki testów porównawczych w powyższych odpowiedziach nie są wiarygodne (przynajmniej w Postgres), ponieważ niektóre zapytania DB uruchomione po raz drugi mogą być znacznie szybsze niż uruchomione za pierwszym razem, dzięki pamięci podręcznej DB. I niestety nie ma łatwego sposobu na wyłączenie pamięci podręcznej w Postgres, aby te testy były wiarygodne.
źródło
Wraz z użyciem
RANDOM()
możesz również wrzucić to do zakresu:Lub, jeśli nie podoba ci się to jako zakres, po prostu wrzuć to do metody klasowej. Teraz
Thing.random
działa razem zThing.random(n)
.źródło
W zależności od znaczenia słowa „losowe” i tego, co faktycznie chcesz zrobić,
take
może wystarczyć.Przez „znaczenie” losowości mam na myśli:
Na przykład do testowania dane próbne i tak mogły zostać utworzone losowo, więc
take
jest więcej niż wystarczające, a szczerze mówiąc, nawetfirst
.https://guides.rubyonrails.org/active_record_querying.html#take
źródło