Rails 3: obtener un registro aleatorio

Por lo tanto, he encontrado varios ejemplos para encontrar un registro aleatorio en Rails 2: el método preferido parece ser:

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

Al ser algo así como novato, no estoy seguro de cómo se podría construir con la nueva syntax de búsqueda en Rails 3.

Entonces, ¿cuál es el “Rails 3 Way” para encontrar un registro aleatorio?

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

o

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

En realidad, en Rails 3 todos los ejemplos funcionarán. Pero usar el orden RANDOM es bastante lento para tablas grandes pero más estilo sql

UPD. Puede usar el siguiente truco en una columna indexada (syntax de PostgreSQL):

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

Estoy trabajando en un proyecto ( Rails 3.0.15, ruby ​​1.9.3-p125-perf ) donde el db está en localhost y la tabla de usuarios tiene un poco más de 100K registros .

Utilizando

ordenar por RAND ()

es bastante lento

User.order (“RAND (id)”) primero

se convierte

SELECCIONAR users . * DE users ORDEN POR RAND (id) LÍMITE 1

y toma de 8 a 12 segundos para responder !!

Registro de Rails:

Carga de usuario (11030.8ms) SELECCIONAR users . * DE users ORDEN POR RAND () LÍMITE 1

de la explicación de 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 | +----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+ 

Puede ver que no se utiliza ningún índice ( possible_keys = NULL ), se crea una tabla temporal y se requiere un pase adicional para recuperar el valor deseado ( extra = Uso temporal; Uso del filesort ).

Por otro lado, dividiendo la consulta en dos partes y usando Ruby, tenemos una mejora razonable en el tiempo de respuesta.

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

(; nil para uso de consola)

Registro de Rails:

Carga de usuario (25.2ms) ID SELECCIONADO DE users Carga de usuario (0.2ms) SELECCIONAR users . * DE users DONDE los users . id = 106854 LÍMITE 1

y la explicación de mysql demuestra por qué:

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

ahora podemos usar solo índices y la clave primaria y hacer el trabajo unas 500 veces más rápido.

ACTUALIZAR:

como se señala por icantbecool en los comentarios, la solución anterior tiene un defecto si hay registros eliminados en la tabla.

Una solución en eso puede ser

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

que se traduce en dos consultas

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

y se ejecuta en aproximadamente 500ms.

Si usa Postgres

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

Si usa MySQL

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

En ambos casos, selecciona 5 registros aleatoriamente de la tabla Usuarios. Aquí está la consulta SQL real en la consola.

 SELECT * FROM users ORDER BY RANDOM() LIMIT 5 

Hice una joya de los Rails 3 para hacer esto que funciona mejor en mesas grandes y le permite encadenar relaciones y scopes:

https://github.com/spilliton/randumb

(editar): El comportamiento predeterminado de mi gem básicamente utiliza el mismo enfoque que el anterior, pero tienes la opción de usar el método anterior si quieres 🙂

Muchas de las respuestas publicadas en realidad no funcionarán bien en tablas bastante grandes (1+ millones de filas). El orden aleatorio toma rápidamente unos segundos, y hacer un recuento de la tabla también lleva bastante tiempo.

Una solución que me funciona bien en esta situación es usar RANDOM() con una condición where:

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

En una tabla con más de un millón de filas, esta consulta generalmente toma menos de 2 ms.

aquí vamos

carriles de camino

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

uso

 Model.random #returns single random object 

o el segundo pensamiento es

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

uso:

 Model.random #returns shuffled collection 

Esto fue muy útil para mí, sin embargo, necesitaba un poco más de flexibilidad, así que esto es lo que hice:

Caso 1: Encontrar una fuente de registro aleatorio : trevor turk site
Agregue esto al modelo 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 

entonces en tu controlador puedes llamar a algo como esto

 @thing = Thing.random 

Caso 2: Encontrar fuente de múltiples registros aleatorios (sin repeticiones) : no puedo recordar
Necesitaba encontrar 10 registros aleatorios sin repeticiones, así que esto es lo que encontré funcionó
En tu controlador:

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

Esto encontrará 10 registros aleatorios, sin embargo, vale la pena mencionar que si la base de datos es particularmente grande (millones de registros), esto no sería ideal, y el rendimiento se verá obstaculizado. Se realizará bien hasta unos miles de registros que fue suficiente para mí.

El método Ruby para elegir aleatoriamente un artículo de una lista es sample . Queriendo crear una sample eficiente para ActiveRecord, y en base a las respuestas anteriores, utilicé:

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

Puse esto en lib/ext/sample.rb y luego lo lib/ext/sample.rb con esto en config/initializers/monkey_patches.rb :

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

Funciona en Rails 5 y es DB agnóstico:

Esto en tu controlador:

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

Por supuesto, puede poner esto en una preocupación como se muestra aquí .

aplicación / modelos / inquietudes / 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 

entonces…

app / models / book.rb

 class Book < ActiveRecord::Base include Randomable end 

Entonces puedes usar simplemente haciendo:

 Books.random 

o

 Books.random(3) 

Puede usar sample () en ActiveRecord

P.ej

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

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

Si usa Oracle

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

Salida

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

Recomiendo encarecidamente esta joya para registros aleatorios, que está especialmente diseñada para la tabla con muchas filas de datos:

https://github.com/haopingfan/quick_random_records

Todas las demás respuestas funcionan mal con una gran base de datos, excepto esta joya:

  1. quick_random_records solo costó 4.6ms total.

enter image description here

  1. la respuesta aceptada User.order('RAND()').limit(10) cuesta 733.0ms .

enter image description here

  1. el enfoque de offset costó 245.4ms total.

enter image description here

  1. el User.all.sample(10) costó 573.4ms .

enter image description here

Nota: Mi tabla solo tiene 120,000 usuarios. Cuantos más registros tenga, más enorme será la diferencia de rendimiento.


ACTUALIZAR:

Realice en la tabla con 550,000 filas

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

enter image description here

  1. gem: quick_random_records solo costó 6.4ms totalmente

enter image description here

Una forma muy fácil de obtener múltiples registros aleatorios de la tabla. Esto hace 2 consultas baratas.

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

Puede cambiar el “3” por la cantidad de registros aleatorios que desee.

Me encontré con este problema desarrollando una pequeña aplicación en la que quería seleccionar una pregunta aleatoria de mi DB. Solía:

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

Y está funcionando bien para mí. No puedo hablar sobre el rendimiento de los DB más grandes, ya que esta es solo una pequeña aplicación.