Na czym polega 'Rails 4 Way' polegający na znalezieniu pewnej liczby przypadkowych rekordów?

User.find(:all, :order => "RANDOM()", :limit => 10) tak zrobiłem w Rails 3.

User.all(:order => "RANDOM()", :limit => 10) tak myślałem, że Rails 4 to zrobi, ale to wciąż daje mi ostrzeżenie o Deprecacji:

DEPRECATION WARNING: Relation#all is deprecated. If you want to eager-load a relation, you can call #load (e.g. `Post.where(published: true).load`). If you want to get an array of records from a relation, you can call #to_a (e.g. `Post.where(published: true).to_a`).
Author: justindao, 2013-06-28

10 answers

Zamiast tego będziesz chciał użyć metod order i limit. Możesz pozbyć się all.

Dla PostgreSQL i SQLite:

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

Lub dla MySQL:

User.order("RAND()").limit(10)
 112
Author: Dylan Markow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-05 02:58:19

Ponieważ funkcja losowa może się zmieniać dla różnych baz danych, polecam użycie następującego kodu:

User.offset(rand(User.count)).first

Oczywiście jest to przydatne tylko wtedy, gdy szukasz tylko jednego rekordu.

Jeśli Chcesz Dostać więcej tego, możesz zrobić coś w stylu:

User.offset(rand(User.count) - 10).limit(10)

- 10 ma zapewnić, że otrzymasz 10 rekordów w przypadku, gdy rand zwróci liczbę większą niż count - 10.

pamiętaj, że zawsze otrzymasz 10 kolejnych rekordów.

 39
Author: maurimiranda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-14 22:53:01

Myślę, że najlepszym rozwiązaniem jest tak naprawdę zamawianie losowo w bazie danych. Ale jeśli chcesz uniknąć konkretnej funkcji losowej z bazy danych, możesz użyć podejścia pluck i shuffle.

Dla jednego rekordu:

User.find(User.pluck(:id).shuffle.first)

Dla więcej niż jednego rekordu:

User.where(id: User.pluck(:id).sample(10))
 24
Author: Prodis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-21 08:44:09

Sugerowałbym zrobienie tego zakresu, jak można go potem łańcuchować:

class User < ActiveRecord::Base
  scope :random, -> { order(Arel::Nodes::NamedFunction.new('RANDOM', [])) }
end 

User.random.limit(10)
User.active.random.limit(10)
 12
Author: Kyle Decot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-16 21:03:31

Choć nie jest to najszybsze rozwiązanie, Lubię zwięzłość:

User.ids.sample(10)

Metoda .ids daje tablicę identyfikatorów użytkownika i .sample(10) wybiera 10 losowych wartości z tej tablicy.

 8
Author: evaneykelen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-01 09:36:26

Zdecydowanie polecam ten gem dla losowych rekordów, który jest specjalnie zaprojektowany dla tabeli 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:

  1. quick_random_records kosztuje tylko 4.6ms całkowicie.

Tutaj wpisz opis obrazka

  1. zaakceptowana odpowiedź User.order('RAND()').limit(10) koszt 733.0ms.

Tutaj wpisz opis obrazka

  1. the offset approach cost 245.4ms totally.

Tutaj wpisz opis obrazka

  1. koszt podejścia User.all.sample(10) 573.4ms.

Tutaj wpisz opis obrazka

Uwaga: mój stół ma tylko 120 000 użytkowników. Im więcej rekordów masz, tym większa będzie różnica w wydajności.


Aktualizacja:

Wykonaj na tabeli z 550 000 wierszy

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

Tutaj wpisz opis obrazka

  1. gem: quick_random_records tylko koszt 6.4ms całkowicie

Tutaj wpisz opis obrazka

 6
Author: Derek Fan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-29 09:04:52

Dla MYSQL to mi pomogło:

User.order("RAND()").limit(10)
 1
Author: beesasoh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-19 13:29:47

Możesz zadzwonić .sample na płytach, jak: User.all.sample(10)

 0
Author: s2t2,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-03 13:56:43

Odpowiedź @ maurimiranda User.offset(rand(User.count)).first nie jest dobra, jeśli potrzebujemy 10 losowych rekordów, ponieważ User.offset(rand(User.count) - 10).limit(10) zwróci sekwencję 10 rekordów z losowej pozycji, nie są one "Total random", prawda? Więc musimy wywołać tę funkcję 10 razy, aby uzyskać 10 "total losowo".

Poza tym offset nie jest również dobry, jeśli funkcja losowa zwraca wysoką wartość. Jeśli Twoje zapytanie wygląda jak offset: 10000 i limit: 20, generuje 10 020 wierszy i wyrzuca pierwsze 10 000 z oni, co jest bardzo drogie. Więc zadzwoń 10 razy offset.limit nie jest skuteczny.

Więc pomyślałem, że w przypadku, gdy chcemy tylko jednego losowego użytkownika, to User.offset(rand(User.count)).first może lepiej (przynajmniej możemy poprawić buforowanie użytkownika.hrabia).

Ale jeśli chcemy 10 losowych użytkowników lub więcej, to User.order("RAND()").limit(10) powinno być lepiej.

 0
Author: Lam Phan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-11 10:54:58

Oto szybkie rozwiązanie.. obecnie używa go z ponad 1,5 miliona rekordów i uzyskuje przyzwoitą wydajność. Najlepszym rozwiązaniem byłoby buforowanie jednego lub więcej losowych zestawów rekordów, a następnie odświeżanie ich za pomocą workera w tle w pożądanym przedziale czasowym.

Utworzony random_records_helper.rb Plik:

module RandomRecordsHelper

 def random_user_ids(n)
    user_ids = []
    user_count = User.count
    n.times{user_ids << rand(1..user_count)}
    return user_ids
 end

W kontrolerze:

@users = User.where(id: random_user_ids(10))

Jest to znacznie szybsze niż metoda .order("RANDOM()").limit(10) - przeszedłem z czasu ładowania 13 S do 500ms.

 -4
Author: Kevin Francis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-07 15:37:42