ruby-on-rails ruby ruby-on-rails-3 activerecord random

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 . * DE users 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 . * 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 rieles:

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.


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.



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;