Pregunta

Estoy en la necesidad de conseguir un registro aleatorio de una tabla a través de ActiveRecord. He seguido el ejemplo de Jamis Buck a partir de 2006 .

Sin embargo, he también ser igualada por otro camino a través de una búsqueda en Google (no puede atributo con un enlace debido a las nuevas restricciones de usuario):

 rand_id = rand(Model.count)
 rand_record = Model.first(:conditions => ["id >= ?", rand_id])

Tengo curiosidad por cómo otros aquí lo han hecho o si alguien sabe qué manera sería más eficiente.

¿Fue útil?

Solución

No he encontrado una forma ideal de hacer esto sin al menos dos consultas.

A continuación se utiliza un número generado aleatoriamente (hasta el actual número de registros) como un el desplazamiento .

offset = rand(Model.count)

# Rails 4
rand_record = Model.offset(offset).first

# Rails 3
rand_record = Model.first(:offset => offset)

Para ser honesto, yo sólo he estado utilizando ORDER BY RAND () o al azar () (dependiendo de la base de datos). No es un problema de rendimiento, si usted no tiene un problema de rendimiento.

Otros consejos

En Carriles 4 y 5 , usando Postgresql o SQLite , utilizando RANDOM():

Model.order('RANDOM()').first

Es de suponer que la misma funcione para MySQL con RAND()

Model.order('RAND()').first

Este es de aproximadamente 2,5 veces más rápido que el enfoque en el respuesta aceptada .

Advertencia . Esto es lento para grandes conjuntos de datos con millones de registros, por lo que es posible que desee agregar una cláusula limit

Su código de ejemplo, comenzará a comportarse incorrectamente una vez que se eliminan los registros (que injustamente favorecerá elementos con identificadores de menores)

Usted es probablemente mejor usar los métodos aleatorios dentro de su base de datos. Estos varían dependiendo de la base de datos que está utilizando, pero: Para => "RAND ()" obras de MySQL y: Para => "random ()" obras para postgres

Model.first(:order => "RANDOM()") # postgres example

Evaluación comparativa de estos dos métodos en MySQL 1.5.49, Ruby 1.9.2p180 en un vector de productos con + 5 millones de registros:

def random1
  rand_id = rand(Product.count)
  rand_record = Product.first(:conditions => [ "id >= ?", rand_id])
end

def random2
  if (c = Product.count) != 0
    Product.find(:first, :offset =>rand(c))
  end
end

n = 10
Benchmark.bm(7) do |x|
  x.report("next id:") { n.times {|i| random1 } }
  x.report("offset:")  { n.times {|i| random2 } }
end


             user     system      total        real
next id:  0.040000   0.000000   0.040000 (  0.225149)
offset :  0.020000   0.000000   0.020000 ( 35.234383)

Offset en MySQL parece ser mucho más lento.

editar También probé

Product.first(:order => "RAND()")

Pero tuve que matarla después de ~ 60 segundos. MySQL fue "Copia a TMP tabla en el disco". Eso no va a trabajar.

No tiene por qué ser tan difícil.

ids = Model.pluck(:id)
random_model = Model.find(ids.sample)

pluck devuelve una matriz de todos los ID de la tabla. El método sample en la matriz, devuelve un ID aleatorio a partir de la matriz.

Esto debería funcionar bien, con igual probabilidad de selección y apoyo para mesas con filas eliminadas. Incluso se puede mezclar con limitaciones.

User.where(favorite_day: "Friday").pluck(:id)

Y de esta manera recoger un usuario al azar que le gusta los viernes en lugar de cualquier usuario.

Me hizo una joya carriles 3 a manejar esto:

https://github.com/spilliton/randumb

Se le hace hacer cosas como esta:

Model.where(:column => "value").random(10)

No se aconseja que los que utiliza esta solución, pero si por alguna razón por la que realmente desea seleccionar aleatoriamente un registro mientras que sólo haciendo consulta una base de datos, se puede utilizar el método sample de la < a href = "http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-sample" rel = "noreferrer"> Rubí clase array, que le permite seleccionar un elemento de azar de una matriz.

Model.all.sample

Este método sólo requiere la consulta de base de datos, pero es significativamente más lento que otras alternativas como Model.offset(rand(Model.count)).first que requieren dos consultas de bases de datos, aunque este último todavía se prefiere.

utilizo este tan a menudo desde la consola extiendo ActiveRecord en un inicializador - Carriles 4 ejemplo:

class ActiveRecord::Base
  def self.random
    self.limit(1).offset(rand(self.count)).first
  end
end

Entonces puede llamar Foo.random para traer de vuelta un registro aleatorio.

Una consulta en Postgres:

User.order('RANDOM()').limit(3).to_sql # Postgres example
=> "SELECT "users".* FROM "users" ORDER BY RANDOM() LIMIT 3"

El uso de un desplazamiento, dos consultas:

offset = rand(User.count) # returns an integer between 0 and (User.count - 1)
Model.offset(offset).limit(1)

La lectura de todos ellos no me dio mucha confianza acerca de cuál de estos funcionaría mejor en mi situación particular con los carriles 5 y MySQL / Maria 5.5. Así que probado algunas de las respuestas en ~ 65000 registros, y tienen dos tomar domicilio:

  1. RAND () con un limit es un claro ganador.
  2. No utilice pluck + sample.
def random1
  Model.find(rand((Model.last.id + 1)))
end

def random2
  Model.order("RAND()").limit(1)
end

def random3
  Model.pluck(:id).sample
end

n = 100
Benchmark.bm(7) do |x|
  x.report("find:")    { n.times {|i| random1 } }
  x.report("order:")   { n.times {|i| random2 } }
  x.report("pluck:")   { n.times {|i| random3 } }
end

              user     system      total        real
find:     0.090000   0.000000   0.090000 (  0.127585)
order:    0.000000   0.000000   0.000000 (  0.002095)
pluck:    6.150000   0.000000   6.150000 (  8.292074)

Esta respuesta sintetiza, valida y actualizaciones respuesta de Mohamed, así como el comentario de Nami WANG en el mismo comentario y Florian de Pilz en la respuesta aceptada - por favor enviar hasta la califican a ellos

!

Puede utilizar el método Array sample, la sample método devuelve un objeto al azar de una matriz, con el fin de utilizarlo sólo tiene que Exec en un simple consulta ActiveRecord que devuelve una colección, por ejemplo:

User.all.sample

devolverá algo como esto:

#<User id: 25, name: "John Doe", email: "admin@example.info", created_at: "2018-04-16 19:31:12", updated_at: "2018-04-16 19:31:12">

Si es necesario seleccionar algunos resultados aleatorios dentro de ámbito especificado

scope :male_names, -> { where(sex: 'm') }
number_of_results = 10

rand = Names.male_names.pluck(:id).sample(number_of_results)
Names.where(id: rand)

recomienda fuertemente esta joya para los registros al azar, que está especialmente diseñado para la tabla con una gran cantidad de filas de datos:

https://github.com/haopingfan/quick_random_records

Todas las otras respuestas un mal desempeño con gran base de datos, a excepción de esta joya:

  1. quick_random_records único costo 4.6ms totalmente.

introducir descripción de la imagen aquí

  1. User.order('RAND()').limit(10) el costo 733.0ms.

introducir descripción de la imagen aquí

  1. la respuesta aceptada offset costo enfoque totalmente 245.4ms.

introducir descripción de la imagen aquí

  1. User.all.sample(10) el costo enfoque 573.4ms.

introducir descripción de la imagen aquí


Nota: Mi tabla sólo tiene 120.000 usuarios. Los registros más tenga, más enorme la diferencia de rendimiento será.

El método de Ruby para escoger al azar un elemento de una lista es sample. Querer crear un sample eficiente para ActiveRecord, y en base a las respuestas anteriores, he utilizado:

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

Pongo esto en lib/ext/sample.rb y luego cargarla con esto en config/initializers/monkey_patches.rb:

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

Esta será una consulta si el tamaño del modelo que ya se almacena en caché y dos de otro modo.

Carriles 4,2 y Oracle :

Para Oracle se puede establecer un alcance en su modelo de esta manera:

scope :random_order, -> {order('DBMS_RANDOM.RANDOM')}

o

scope :random_order, -> {order('DBMS_RANDOM.VALUE')}

Y a continuación, para un ejemplo de la llamada de esta manera:

Model.random_order.take(10)

o

Model.random_order.limit(5)

Por supuesto podría también hacer un pedido sin un ámbito de esta manera:

Model.all.order('DBMS_RANDOM.RANDOM') # or DBMS_RANDOM.VALUE respectively

Para MySQL base de datos de prueba:. Model.order ( "RAND ()") primero

Si está utilizando PostgreSQL 9.5+, usted puede tomar ventaja de TABLESAMPLE para seleccionar un registro aleatorio.

Los dos métodos de muestreo por defecto (SYSTEM y BERNOULLI) requieren que se especifique el número de filas a devolver como un porcentaje del número total de filas en la tabla.

-- Fetch 10% of the rows in the customers table.
SELECT * FROM customers TABLESAMPLE BERNOULLI(10);

Esto requiere el conocimiento de la cantidad de registros en la tabla para seleccionar el porcentaje apropiado, que puede no ser fácil de encontrar rápidamente. Afortunadamente, existe la tsm_system_rows módulo que permite para especificar el número de filas a devolver directamente.

CREATE EXTENSION tsm_system_rows;

-- Fetch a single row from the customers table.
SELECT * FROM customers TABLESAMPLE SYSTEM_ROWS(1);

Para utilizar esta dentro de ActiveRecord, primero habilitar la extensión dentro de una migración:

class EnableTsmSystemRowsExtension < ActiveRecord::Migration[5.0]
  def change
    enable_extension "tsm_system_rows"
  end
end

A continuación, modifique la cláusula from de la consulta:

customer = Customer.from("customers TABLESAMPLE SYSTEM_ROWS(1)").first

No sé si el método de muestreo SYSTEM_ROWS será totalmente aleatoria o si simplemente devuelve la primera fila de una página al azar.

La mayor parte de esta información se tomó de un 2ndQuadrant entrada de blog escrito por Gulcin Yildirim .

Después de ver tantas respuestas que decidí referencia a todos en mi PostgreSQL (9.6.3) de base de datos. Yo uso una pequeña mesa de 100.000 y se deshizo de la Model.order ( "random ()"). Primero ya que era ya dos órdenes de magnitud más lento.

El uso de una mesa con 2.500.000 entradas con 10 columnas las manos abajo ganador fue el método de desplumar siendo casi 8 veces más rápido que el segundo clasificado (offset. Yo sólo encontré esto en un servidor local, de modo que el número podría ser inflado pero es lo suficientemente grande que el método de desplume es lo que voy a terminar usando. también vale la pena señalar que esta causa podría cuestiones es que arrancan más de 1 resultado a la vez, ya que cada uno de esos será único aka menos aleatoria.

Pluck gana corriendo 100 veces en mi mesa 25000000 fila Editar: en realidad este tiempo incluye las agallas en el bucle si me lo saco que se ejecuta casi tan rápido tan simple iteración en la ID. Sin embargo; lo hace tomar una buena cantidad de memoria RAM.

RandomModel                 user     system      total        real
Model.find_by(id: i)       0.050000   0.010000   0.060000 (  0.059878)
Model.offset(rand(offset)) 0.030000   0.000000   0.030000 ( 55.282410)
Model.find(ids.sample)     6.450000   0.050000   6.500000 (  7.902458)

Estos son los datos que ejecuta 2000 veces en mi mesa de 100.000 fila para descartar al azar

RandomModel       user     system      total        real
find_by:iterate  0.010000   0.000000   0.010000 (  0.006973)
offset           0.000000   0.000000   0.000000 (  0.132614)
"RANDOM()"       0.000000   0.000000   0.000000 ( 24.645371)
pluck            0.110000   0.020000   0.130000 (  0.175932)

Soy nuevo a RoR pero tengo que esto funcione para mí:

 def random
    @cards = Card.all.sort_by { rand }
 end

Vino de:

Cómo azar especie (scramble) una matriz en Rubí ?

¿Qué hay que hacer:

rand_record = Model.find(Model.pluck(:id).sample)

Para mí es mucho más clara

Intento de este ejemplo de Sam en mi aplicación con rieles 4.2.8 de referencia (pongo 1..Category.count para al azar, ya que si el azar tiene un 0 se producirá un error (ActiveRecord :: RecordNotFound: No se pudo 't hallazgo Categoría 'id'= 0)) y la mina fue:

 def random1
2.4.1 :071?>   Category.find(rand(1..Category.count))
2.4.1 :072?>   end
 => :random1
2.4.1 :073 > def random2
2.4.1 :074?>    Category.offset(rand(1..Category.count))
2.4.1 :075?>   end
 => :random2
2.4.1 :076 > def random3
2.4.1 :077?>   Category.offset(rand(1..Category.count)).limit(rand(1..3))
2.4.1 :078?>   end
 => :random3
2.4.1 :079 > def random4
2.4.1 :080?>    Category.pluck(rand(1..Category.count))
2.4.1 :081?>
2.4.1 :082 >     end
 => :random4
2.4.1 :083 > n = 100
 => 100
2.4.1 :084 > Benchmark.bm(7) do |x|
2.4.1 :085 >     x.report("find") { n.times {|i| random1 } }
2.4.1 :086?>   x.report("offset") { n.times {|i| random2 } }
2.4.1 :087?>   x.report("offset_limit") { n.times {|i| random3 } }
2.4.1 :088?>   x.report("pluck") { n.times {|i| random4 } }
2.4.1 :089?>   end

                  user      system      total     real
find            0.070000   0.010000   0.080000 (0.118553)
offset          0.040000   0.010000   0.050000 (0.059276)
offset_limit    0.050000   0.000000   0.050000 (0.060849)
pluck           0.070000   0.020000   0.090000 (0.099065)

.order('RANDOM()').limit(limit) se ve bien, pero es lento para tablas grandes porque tiene que ir a buscar y clasificar todas las filas, incluso si es limit 1 (internamente en la base de datos, pero no en los carriles). No estoy seguro acerca de MySQL, pero esto sucede en Postgres. Más explicación en aquí y aquí .

Una solución para tablas grandes es donde .from("products TABLESAMPLE SYSTEM(0.5)") medios 0.5 0.5%. Sin embargo, creo que esta solución sigue siendo lenta si tiene condiciones WHERE que filtrar una gran cantidad de filas. Supongo que es porque TABLESAMPLE SYSTEM(0.5) recuperar todas las filas antes de que se apliquen condiciones WHERE.

Otra solución para tablas grandes (pero no muy aleatorio) es:

products_scope.limit(sample_size).sample(limit)

donde sample_size puede ser 100 (pero no demasiado grande de lo contrario es lento y consume una gran cantidad de memoria), y puede ser limit 1. Tenga en cuenta que aunque esto es rápido, pero en realidad no es aleatoria, es aleatoria dentro de los registros sample_size solamente.

PS: Resultados de referencia en respuestas anteriores no son fiables (por lo menos en Postgres), ya que algunas consultas de base de datos que se ejecutan en el segundo tiempo, puede ser significativamente más rápido que corriendo a primera hora, gracias a la caché de base de datos. Y, por desgracia no hay una manera fácil de desactivar la memoria caché en Postgres para hacer estos puntos de referencia fiable.

Una pregunta muy antigua pero con:

rand_record = Model.all.shuffle

Se obtuvo una tabla de registros, ordenar por orden aleatorio. No hay necesidad de gemas o secuencias de comandos.

Si desea un registro:

rand_record = Model.all.shuffle.first

Junto con el uso RANDOM(), también puede tirar esto en un ámbito de aplicación:

class Thing
  scope :random, -> (limit = 1) {
    order('RANDOM()').
    limit(limit)
  }
end

O, si no te gusta que, como el alcance, simplemente tirarlo en un método de clase. Ahora obras Thing.random junto con Thing.random(n).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top