registro aleatorio en ActiveRecord
-
02-10-2019 - |
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.
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:
- RAND () con un
limit
es un claro ganador. - 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:
- quick_random_records único costo
4.6ms
totalmente.
-
User.order('RAND()').limit(10)
el costo733.0ms
.
- la respuesta aceptada
offset
costo enfoque totalmente245.4ms
.
-
User.all.sample(10)
el costo enfoque573.4ms
.
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:
¿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)
.