ruby on rails - Rails 3: obtener un registro aleatorio
ruby-on-rails ruby-on-rails-3 (13)
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 }
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 sintaxis de búsqueda en Rails 3.
Entonces, ¿cuál es el "Rails 3 Way" para encontrar un registro aleatorio?
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í.
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
. * DEusers
ORDEN POR RAND (id) LÍMITE 1
y toma de 8 a 12 segundos para responder !!
Registro de rieles:
Carga de usuario (11030.8ms) SELECCIONAR
users
. * DEusers
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 rieles:
Carga de usuario (25.2ms) ID SELECCIONADO DE
users
Carga de usuario (0.2ms) SELECCIONARusers
. * DEusers
DONDE losusers
.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.
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 here .
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)
Hice una joya de los rieles 3 para hacer esto que funciona mejor en mesas grandes y le permite encadenar relaciones y alcances:
https://github.com/spilliton/randumb
(editar): El comportamiento predeterminado de mi gema básicamente utiliza el mismo enfoque que el anterior, pero tienes la opción de usar el método anterior si quieres :)
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.
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.
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
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
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.
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
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 (sintaxis de PostgreSQL):
select *
from my_table
where id >= trunc(
random() * (select max(id) from my_table) + 1
)
order by id
limit 1;